From 2aa0e648f92a8a210bfc4ebf2b4b6bb47369aa7d Mon Sep 17 00:00:00 2001 From: meywood <105049338+meywood@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:15:24 +0000 Subject: [PATCH 1/2] issues/371 - CalltableSerializationEnvelopeBuilderTest initial implemenation. Also impored fork of dev.oak3.sbs4j codebase as needed to implement U16 serialization. --- build.gradle | 3 +- ...CalltableSerializationEnvelopeBuilder.java | 125 ++++++ .../sdk/model/transaction/field/Field.java | 106 +++++ .../dev/oak3/sbs4j/DeserializerBuffer.java | 380 +++++++++++++++++ .../java/dev/oak3/sbs4j/SerializerBuffer.java | 402 ++++++++++++++++++ .../sbs4j/exception/NoSuchTypeException.java | 12 + .../ValueDeserializationException.java | 16 + .../ValueSerializationException.java | 16 + .../interfaces/DeserializableObject.java | 19 + .../sbs4j/interfaces/SerializableObject.java | 19 + .../java/dev/oak3/sbs4j/util/ByteUtils.java | 124 ++++++ .../sdk/model/entity/StateGetEntityTest.java | 3 +- ...tableSerializationEnvelopeBuilderTest.java | 90 ++++ .../casper/sdk/service/TransactionTests.java | 7 - .../oak3/sbs4j/DeserializerBufferTest.java | 257 +++++++++++ .../dev/oak3/sbs4j/SerializerBufferTest.java | 298 +++++++++++++ src/test/java/dev/oak3/sbs4j/model/Point.java | 70 +++ .../java/dev/oak3/sbs4j/model/Polygon.java | 64 +++ .../java/dev/oak3/sbs4j/model/Sphere.java | 55 +++ .../java/dev/oak3/sbs4j/model/TestData.java | 155 +++++++ .../dev/oak3/sbs4j/util/ByteUtilsTest.java | 87 ++++ 21 files changed, 2297 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java create mode 100644 src/main/java/com/casper/sdk/model/transaction/field/Field.java create mode 100644 src/main/java/dev/oak3/sbs4j/DeserializerBuffer.java create mode 100644 src/main/java/dev/oak3/sbs4j/SerializerBuffer.java create mode 100644 src/main/java/dev/oak3/sbs4j/exception/NoSuchTypeException.java create mode 100644 src/main/java/dev/oak3/sbs4j/exception/ValueDeserializationException.java create mode 100644 src/main/java/dev/oak3/sbs4j/exception/ValueSerializationException.java create mode 100644 src/main/java/dev/oak3/sbs4j/interfaces/DeserializableObject.java create mode 100644 src/main/java/dev/oak3/sbs4j/interfaces/SerializableObject.java create mode 100644 src/main/java/dev/oak3/sbs4j/util/ByteUtils.java create mode 100644 src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java create mode 100644 src/test/java/dev/oak3/sbs4j/DeserializerBufferTest.java create mode 100644 src/test/java/dev/oak3/sbs4j/SerializerBufferTest.java create mode 100644 src/test/java/dev/oak3/sbs4j/model/Point.java create mode 100644 src/test/java/dev/oak3/sbs4j/model/Polygon.java create mode 100644 src/test/java/dev/oak3/sbs4j/model/Sphere.java create mode 100644 src/test/java/dev/oak3/sbs4j/model/TestData.java create mode 100644 src/test/java/dev/oak3/sbs4j/util/ByteUtilsTest.java diff --git a/build.gradle b/build.gradle index 45c59d128..ec3447fa3 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ apply plugin: 'java' group = 'network.casper' // Version number update for release -version='2.7.0-BETA.2' +version='2.7.0-BETA.3' sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -24,7 +24,6 @@ repositories { } dependencies { - implementation "dev.oak3:sbs4j:${sbs4jVersion}" implementation "io.github.oak:jsonrpc4j:${jsonrpc4jVersion}" implementation "org.bouncycastle:bcpkix-jdk15on:${bouncyCastleVersion}" implementation "org.web3j:core:${web3jVersion}" diff --git a/src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java b/src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java new file mode 100644 index 000000000..1e56c2c92 --- /dev/null +++ b/src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java @@ -0,0 +1,125 @@ +package com.casper.sdk.model.transaction.field; + +import com.casper.sdk.exception.NoSuchTypeException; +import com.casper.sdk.model.clvalue.serde.CasperSerializableObject; +import com.casper.sdk.model.clvalue.serde.Target; +import dev.oak3.sbs4j.DeserializerBuffer; +import dev.oak3.sbs4j.SerializerBuffer; +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.exception.ValueSerializationException; +import dev.oak3.sbs4j.interfaces.DeserializableObject; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * The call table serialization envelope builder used to serialize and deserialize transaction fields/ + * + * @author ian@meywood.com + */ +@AllArgsConstructor +@NoArgsConstructor +@Setter +public class CalltableSerializationEnvelopeBuilder implements CasperSerializableObject, DeserializableObject { + + /** The fields to serialize */ + private List fields = new ArrayList<>(); + /** The current field index */ + private long currentFieldIndex = -1; + /** The expected number of fields */ + private long expectedFields; + /** The offset of the current field om the serialized bytes */ + private long offset = 0; + /** The total size of all field values when serialized */ + private long size = 0; + + public void addField(final int index, final T value) throws ValueSerializationException { + this.addField(new Field(index, this.offset, value)); + } + + /** + * Add a field to the envelope + * + * @param index the zero based index + * @param value the bytes of the value to add + */ + public void addFieldBytes(final int index, final byte[] value) { + + this.addField(new Field((short) index, offset, value)); + } + + public void addField(final Field field) { + if (this.currentFieldIndex >= field.getIndex()) { + throw new IllegalArgumentException("Field index must be greater than the previous field index"); + } + + if (this.currentFieldIndex + 1 != field.getIndex()) { + throw new IllegalArgumentException("Field index must be sequential"); + } + + this.fields.add(field); + this.currentFieldIndex = field.getIndex(); + this.size += field.getValue().length; + this.offset += field.getValue().length; + } + + + public T getFieldValue(final int index, final Class clazz) throws ValueDeserializationException { + final Field field = this.fields.get(index); + return field.getValue(clazz); + } + + public byte[] getFieldBytes(final int index) { + return fields.get(index).getValue(); + } + + @Override + public void serialize(final SerializerBuffer ser, final Target target) throws ValueSerializationException, NoSuchTypeException { + + if (this.fields.size() != this.expectedFields) { + throw new IllegalArgumentException("Field index must be expected length " + this.expectedFields); + } + + // Write the number of fields + ser.writeI32(this.fields.size()); + + for (final Field field : this.fields) { + field.serialize(ser, target); + } + + // Write total bytes of all field values + ser.writeU32(this.size); + for (final Field field : this.fields) { + ser.writeByteArray(field.getValue()); + } + } + + @Override + public void deserialize(final DeserializerBuffer deserializerBuffer) throws ValueDeserializationException { + + final long numFields = deserializerBuffer.readU32(); + + for (int i = 0; i < numFields; i++) { + final Field field = new Field(); + field.deserialize(deserializerBuffer); + this.fields.add(field); + } + + this.size = deserializerBuffer.readU32(); + for (Field field : this.fields) { + final long fieldValueLen = getFieldLength(field); + field.setValue(deserializerBuffer.readByteArray((int) fieldValueLen)); + } + } + + private long getFieldLength(final Field field) { + if (field.getIndex() == this.fields.size() - 1) { + return size - field.getOffset(); + } else { + return this.fields.get(field.getIndex() + 1).getOffset() - field.getOffset(); + } + } +} diff --git a/src/main/java/com/casper/sdk/model/transaction/field/Field.java b/src/main/java/com/casper/sdk/model/transaction/field/Field.java new file mode 100644 index 000000000..b02118d5c --- /dev/null +++ b/src/main/java/com/casper/sdk/model/transaction/field/Field.java @@ -0,0 +1,106 @@ +package com.casper.sdk.model.transaction.field; + +import com.casper.sdk.exception.NoSuchTypeException; +import com.casper.sdk.model.clvalue.serde.CasperSerializableObject; +import com.casper.sdk.model.clvalue.serde.Target; +import dev.oak3.sbs4j.DeserializerBuffer; +import dev.oak3.sbs4j.SerializerBuffer; +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.exception.ValueSerializationException; +import dev.oak3.sbs4j.interfaces.DeserializableObject; +import dev.oak3.sbs4j.interfaces.SerializableObject; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * An indexed field with an offset and value. + * + * @author ian@meywood.com + */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class Field implements CasperSerializableObject, DeserializableObject { + + /** The field index */ + private short index; + /** The offset of the field's value when written to bytes */ + private long offset; + /** The field value as bytes */ + private byte[] value; + + public Field(final int index, final long offset, final Object value) throws ValueSerializationException { + final SerializerBuffer serializerBuffer = new SerializerBuffer(); + + if (value instanceof SerializableObject) { + ((SerializableObject) value).serialize(serializerBuffer); + } else if (value instanceof byte[]) { + serializerBuffer.writeByteArray((byte[]) value); + } else if (value.getClass().equals(Byte.class)) { + serializerBuffer.writeU8((Byte) value); + } else if (value.getClass().equals(Long.class)) { + serializerBuffer.writeI64((Long) value); + } else if (value.getClass().equals(Integer.class)) { + serializerBuffer.writeI32((Integer) value); + } else if (value.getClass().equals(Short.class)) { + serializerBuffer.writeU16((Short) value); + } else if (value.getClass().equals(String.class)) { + serializerBuffer.writeString((String) value); + } else { + throw new ValueSerializationException("Unsupported type " + value.getClass().getName()); + } + + this.index = (short) index; + this.offset = offset; + this.value = serializerBuffer.toByteArray(); + } + + @Override + public void serialize(final SerializerBuffer ser, final Target target) throws ValueSerializationException, NoSuchTypeException { + this.serialize(ser); + } + + @Override + public void serialize(final SerializerBuffer ser) throws ValueSerializationException { + ser.writeU16(index); + ser.writeU32(offset); + } + + @Override + public void deserialize(final DeserializerBuffer deser) throws ValueDeserializationException { + this.index = deser.readU16(); + this.offset = deser.readU32(); + } + + @SuppressWarnings("unchecked") + public T getValue(final Class clazz) throws ValueDeserializationException { + final DeserializerBuffer deserializerBuffer = new DeserializerBuffer(this.value); + + if (clazz.isAssignableFrom(DeserializableObject.class)) { + try { + final T value = clazz.getDeclaredConstructor().newInstance(); + ((DeserializableObject) value).deserialize(deserializerBuffer); + return value; + } catch (Exception e) { + throw new ValueDeserializationException("Unsupported type " + clazz.getName(), e); + } + } else if (clazz == byte[].class) { + return (T) deserializerBuffer.readByteArray(this.value.length); + } else if (clazz == Byte.class) { + return (T) (Byte) deserializerBuffer.readU8(); + } else if (clazz == Long.class) { + return (T) (Long) deserializerBuffer.readU32(); + } else if (clazz == Integer.class) { + return (T) (Integer) deserializerBuffer.readI32(); + } else if (clazz == Short.class) { + return (T) (Short) deserializerBuffer.readU16(); + } else if (clazz == String.class) { + return (T) deserializerBuffer.readString(); + } + + throw new IllegalArgumentException("Unsupported type " + clazz.getName()); + } +} diff --git a/src/main/java/dev/oak3/sbs4j/DeserializerBuffer.java b/src/main/java/dev/oak3/sbs4j/DeserializerBuffer.java new file mode 100644 index 000000000..ceeb202e8 --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/DeserializerBuffer.java @@ -0,0 +1,380 @@ +package dev.oak3.sbs4j; + +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.util.ByteUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +/** + * Deserializing methods + * + * @since 0.1.0 + */ +public class DeserializerBuffer { + + private final ByteBuffer buffer; + + private static final Logger LOGGER = LoggerFactory.getLogger(DeserializerBuffer.class); + + private static final String LOG_BUFFER_INIT_MESSAGE_HEX_STRING = "Initializing DeserializerBuffer with hexString: {} and byte order {}"; + private static final String LOG_BUFFER_INIT_MESSAGE = "Initializing DeserializerBuffer with bytes: {} and byte order {}"; + private static final String LOG_BUFFER_VALUE_MESSAGE_STRING = "Buffer value: {}"; + private static final String LOG_SERIALIZED_VALUE_MESSAGE_STRING = "Deserialized value for {}: {}"; + private static final String SERIALIZE_EXCEPTION_OUT_OF_BOUNDS_MESSAGE_STRING = "Value %s out of bounds for expected type %s"; + + /** + * Initializes buffer with serialized bytes from hex-encoded {@link String} + * + * @param hexString hex-encoded {@link String} to deserialize and read from + */ + public DeserializerBuffer(final String hexString) { + this(!hexString.isEmpty() ? ByteUtils.parseHexString(hexString) : new byte[]{}); + } + + /** + * Initializes buffer with serialized bytes from hex-encoded {@link String} + * + * @param hexString hex-encoded {@link String} to deserialize and read from + * @param byteOrder the byte order to be using + */ + public DeserializerBuffer(final String hexString, final ByteOrder byteOrder) { + this(!hexString.isEmpty() ? ByteUtils.parseHexString(hexString) : new byte[]{}, byteOrder); + + LOGGER.debug(LOG_BUFFER_INIT_MESSAGE_HEX_STRING, hexString, byteOrder); + } + + /** + * Initializes buffer with serialized bytes and {@link ByteOrder#LITTLE_ENDIAN} + * + * @param bytes byte array to deserialize and read from + */ + public DeserializerBuffer(final byte[] bytes) { + this(bytes, ByteOrder.LITTLE_ENDIAN); + } + + /** + * Initializes buffer with serialized bytes and byte order + * + * @param bytes byte array to deserialize and read from + * @param byteOrder the byte order to be using + */ + public DeserializerBuffer(final byte[] bytes, final ByteOrder byteOrder) { + this.buffer = ByteBuffer.wrap(bytes); + this.buffer.order(byteOrder); + this.buffer.mark(); + + LOGGER.debug(LOG_BUFFER_INIT_MESSAGE, Arrays.toString(bytes), byteOrder); + } + + /** + * Reads a Boolean value + * + * @return true if 1, while false if 0 + * @throws ValueDeserializationException exception holding information of failure to deserialize a value + */ + public Boolean readBool() throws ValueDeserializationException { + try { + final byte buf = this.buffer.get(); + + LOGGER.debug(LOG_BUFFER_VALUE_MESSAGE_STRING, buf); + + if (buf == 1) { + return true; + } else if (buf == 0) { + return false; + } else { + throw new ValueDeserializationException( + String.format(SERIALIZE_EXCEPTION_OUT_OF_BOUNDS_MESSAGE_STRING, buf, Boolean.class.getSimpleName())); + } + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading boolean from buffer", bufferUnderflowException); + } + } + + /** + * Reads a byte from buffer + * + * @return the byte + */ + public byte readU8() throws ValueDeserializationException { + try { + final byte u8 = this.buffer.get(); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Byte.class.getSimpleName(), u8); + + return u8; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading U8 from buffer", bufferUnderflowException); + } + } + + /** + * Reads a byte from buffer + * + * @return the byte + */ + public short readU16() throws ValueDeserializationException { + try { + final short u16 = this.buffer.getShort(); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Short.class.getSimpleName(), u16); + + return u16; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading U16 from buffer", bufferUnderflowException); + } + } + + + /** + * Reads a byte[] from buffer + * + * @param length the length of the array + * @return the byte array as byte[] + */ + public byte[] readByteArray(final int length) throws ValueDeserializationException { + try { + final byte[] bytes = readBytes(length); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Byte.class.getSimpleName(), bytes); + + return bytes; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading byte array from buffer", bufferUnderflowException); + } + } + + /** + * Reads a float of 32 bits (4 bytes) + * + * @return the number as a float + */ + public float readF32() throws ValueDeserializationException { + try { + final float floatNumber = this.buffer.getFloat(); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Float.class.getSimpleName(), floatNumber); + + return floatNumber; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading F32 from buffer", bufferUnderflowException); + } + } + + /** + * Reads a float of 64 bits (8 bytes) + * + * @return the number as a double + */ + public double readF64() throws ValueDeserializationException { + try { + final double doubleNumber = this.buffer.getDouble(); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Double.class.getSimpleName(), doubleNumber); + + return doubleNumber; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading F64 from buffer", bufferUnderflowException); + } + } + + /** + * Reads a signed int of 32 bits (4 bytes) + * + * @return the number as an int + */ + public int readI32() throws ValueDeserializationException { + try { + final int integerNumber = this.buffer.getInt(); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Long.class.getSimpleName(), integerNumber); + + return integerNumber; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading I32 from buffer", bufferUnderflowException); + } + } + + /** + * Reads an unsigned int of 32 bits (4 bytes) + * + * @return the number as a long + */ + public long readU32() throws ValueDeserializationException { + try { + final int signedInteger = this.buffer.getInt(); + final long unsignedIntegerLong = Integer.toUnsignedLong(signedInteger); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Long.class.getSimpleName(), unsignedIntegerLong); + return unsignedIntegerLong; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading U32 from buffer", bufferUnderflowException); + } + } + + /** + * Reads a signed int of 64 bits (8 bytes) + * + * @return the number as a long + */ + public long readI64() throws ValueDeserializationException { + try { + final long longNumber = this.buffer.getLong(); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, Long.class.getSimpleName(), longNumber); + + return longNumber; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading I64 from buffer", bufferUnderflowException); + } + } + + /** + * Reads an unsigned int of 64 bits (8 bytes) + * + * @return the number as a BigInteger + */ + public BigInteger readU64() throws ValueDeserializationException { + try { + // Since this is a positive (unsigned) number, we should prefix with a zero + // byte to parse correctly + final ByteBuffer bb = ByteBuffer.allocate(9); + bb.put((byte) 0); + bb.putLong(this.buffer.getLong()); + final BigInteger unsignedLong = new BigInteger(bb.array()); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, BigInteger.class.getSimpleName(), unsignedLong); + + return unsignedLong; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading U64 from buffer", bufferUnderflowException); + } + } + + /** + * Reads an unsigned int of 128 bits (16 bytes) max + * + * @return the number as a BigInteger + */ + public BigInteger readU128() throws ValueDeserializationException { + try { + return this.readBigInteger(); + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading U128 from buffer", bufferUnderflowException); + } + } + + /** + * Reads U256 from buffer + * + * @return the number as a BigInteger + */ + public BigInteger readU256() throws ValueDeserializationException { + try { + return this.readBigInteger(); + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading U256 from buffer", bufferUnderflowException); + } + } + + /** + * Reads U512 from buffer + * + * @return the number as a BigInteger + */ + public BigInteger readU512() throws ValueDeserializationException { + try { + return this.readBigInteger(); + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading U512 from buffer", bufferUnderflowException); + } + } + + /** + * Larger numeric values (e.g., U128, U256, U512) serialize as one byte of the + * length of the next number, followed by the two’s complement representation + * with little-endian byte order. + * + * @return the number as a BigInteger + */ + protected BigInteger readBigInteger() { + final byte lengthOfNextNumber = this.buffer.get(); + + LOGGER.debug("Length of next number: {}", lengthOfNextNumber); + + final byte[] buf = new byte[lengthOfNextNumber + 1]; + + this.buffer.get(buf, 1, lengthOfNextNumber); + + LOGGER.debug(LOG_BUFFER_VALUE_MESSAGE_STRING, buf); + + if (this.buffer.order() == ByteOrder.LITTLE_ENDIAN) { + ByteUtils.reverse(buf, 1, buf.length - 1); + } + + final BigInteger bigInt = new BigInteger(buf); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, BigInteger.class.getSimpleName(), bigInt); + + return bigInt; + } + + /** + * Reads a String value from buffer + * + * @return the String read + */ + public String readString() throws ValueDeserializationException { + try { + final int length = this.buffer.getInt(); + + LOGGER.debug("Reading string of length: {}", length); + + final byte[] bufString = new byte[length]; + + this.buffer.get(bufString, 0, length); + + LOGGER.debug(LOG_BUFFER_VALUE_MESSAGE_STRING, bufString); + + final String string = new String(bufString, StandardCharsets.UTF_8); + + LOGGER.debug(LOG_SERIALIZED_VALUE_MESSAGE_STRING, String.class.getSimpleName(), string); + + return string; + } catch (BufferUnderflowException bufferUnderflowException) { + throw new ValueDeserializationException("Error while reading String from buffer", bufferUnderflowException); + } + } + + /** + * Retrieves the backing buffer + * + * @return the Deserializer ByteBuffer + */ + public ByteBuffer getBuffer() { + return this.buffer; + } + + /** + * Reads a specified number of bytes + * + * @param length the number of bytes to read + * @return bytes read + */ + protected byte[] readBytes(int length) { + final byte[] buf = new byte[length]; + + this.buffer.get(buf, 0, length); + + LOGGER.debug(LOG_BUFFER_VALUE_MESSAGE_STRING, buf); + + return buf; + } +} diff --git a/src/main/java/dev/oak3/sbs4j/SerializerBuffer.java b/src/main/java/dev/oak3/sbs4j/SerializerBuffer.java new file mode 100644 index 000000000..f3208f05a --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/SerializerBuffer.java @@ -0,0 +1,402 @@ +package dev.oak3.sbs4j; + +import dev.oak3.sbs4j.exception.NoSuchTypeException; +import dev.oak3.sbs4j.exception.ValueSerializationException; +import dev.oak3.sbs4j.util.ByteUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import static java.util.Objects.requireNonNull; + +/** + * Serializing methods + * + * @since 0.1.0 + */ +public class SerializerBuffer { + + private static final Logger LOGGER = LoggerFactory.getLogger(SerializerBuffer.class); + + private ByteBuffer buffer; + + private static final int INITIAL_CAPACITY = 4096; + + public static final BigInteger ZERO = new BigInteger("0", 10); + public static final BigInteger ONE = new BigInteger("1", 10); + public static final BigInteger TWO = new BigInteger("2", 10); + public static final BigInteger MAX_U64 = TWO.pow(64).subtract(ONE); + public static final BigInteger MAX_U128 = TWO.pow(128).subtract(ONE); + public static final BigInteger MAX_U256 = TWO.pow(256).subtract(ONE); + public static final BigInteger MAX_U512 = TWO.pow(512).subtract(ONE); + + private static final String LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING = "Writing type {} with value: {}"; + private static final String SERIALIZE_EXCEPTION_OUT_OF_BOUNDS_MESSAGE_STRING = "Value %s out of bounds for expected type %s"; + + /** + * Initializes buffer with initial capacity of {@link #INITIAL_CAPACITY} in bytes and {@link ByteOrder#LITTLE_ENDIAN} + */ + public SerializerBuffer() { + this(INITIAL_CAPACITY, ByteOrder.LITTLE_ENDIAN); + } + + /** + * Initializes buffer with initial capacity of {@link #INITIAL_CAPACITY} and given byte order. + * + * @param byteOrder the byte order to be using + */ + public SerializerBuffer(final ByteOrder byteOrder) { + this(INITIAL_CAPACITY, byteOrder); + } + + /** + * Initializes buffer with given initial capacity in bytes and byte order. + * + * @param initialCapacity the initial capacity of the buffer + * @param byteOrder the byte order to be using + */ + public SerializerBuffer(final int initialCapacity, final ByteOrder byteOrder) { + this.buffer = ByteBuffer.allocate(initialCapacity); + this.buffer.order(byteOrder); + this.buffer.mark(); + } + + /** + * Writes a boolean value to the value byte buffer + * + * @param value boolean value to serialize + */ + public void writeBool(final boolean value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Boolean.class.getSimpleName(), + value); + + byte boolByte = Boolean.TRUE.equals(value) ? (byte) 0x01 : (byte) 0x00; + + put(boolByte); + } + + /** + * Writes a single byte value + * + * @param value byte value to serialize + */ + public void writeU8(final byte value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Byte.class.getSimpleName(), value); + + put(value); + } + + /** + * Writes a single byte value + * + * @param value byte value to serialize + */ + public void writeU16(final short value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Short.class.getSimpleName(), value); + + put(value); + } + + + /** + * Writes a byte array value + * + * @param value byte array value to serialize + */ + public void writeByteArray(final byte[] value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, byte[].class.getSimpleName(), value); + + put(value); + } + + /** + * Writes a Float/F32 value + * + * @param value F32 value to serialize + */ + public void writeF32(final float value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Float.class.getSimpleName(), value); + + put(value); + } + + /** + * Writes a Double/F64 value + * + * @param value F64 value to serialize + */ + public void writeF64(final double value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Double.class.getSimpleName(), value); + + put(value); + } + + /** + * Writes an Integer/I32 value + * + * @param value I32 value to serialize + */ + public void writeI32(final int value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Integer.class.getSimpleName(), value); + + put(value); + } + + /** + * Writes an Unsigned Integer (Long)/U32 + * + * @param value U32 value to serialize + */ + public void writeU32(final Long value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Integer.class.getSimpleName(), value); + + put(value.intValue()); + } + + /** + * Writes a Long/I64 value + * + * @param value I64 value to serialize + */ + public void writeI64(final long value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, Long.class.getSimpleName(), value); + + put(value); + } + + /** + * Writes an Unsigned Long (BigInteger)/U64 to the value byte buffer + * + * @param value U64 value to serialize + * error with input/output while reading the byte array + * @throws ValueSerializationException exception holding information of failure to serialize a value + */ + public void writeU64(final BigInteger value) throws ValueSerializationException { + requireNonNull(value); + checkBoundsFor(value, 64); + + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, BigInteger.class.getSimpleName(), + value); + + put(value.longValue()); + } + + /** + * Writes a BigInteger/U128 to the value byte buffer + * + * @param value U128 value to serialize + * error with input/output while reading the byte array + * @throws ValueSerializationException exception holding information of failure to serialize a value + */ + public void writeU128(final BigInteger value) throws ValueSerializationException { + writeBigInteger(value, 128); + } + + /** + * Writes a BigInteger/U256 to the value byte buffer + * + * @param value U256 value to serialize + * error with input/output while reading the byte array + * @throws ValueSerializationException exception holding information of failure to serialize a value + */ + public void writeU256(final BigInteger value) throws ValueSerializationException { + writeBigInteger(value, 256); + } + + /** + * Writes a BigInteger/U512 to the value byte buffer + * + * @param value U512 value to serialize + * error with input/output while reading the byte array + * @throws ValueSerializationException exception holding information of failure to serialize a value + */ + public void writeU512(final BigInteger value) throws ValueSerializationException { + writeBigInteger(value, 512); + } + + /** + * Writes a BigInteger/U128-U256-U512 to the value byte buffer + * + * @param value BigInteger to serialize + * @param size the bit size of BigInteger + * error with input/output while reading the byte array + * @throws ValueSerializationException exception holding information of failure to serialize a value + */ + protected void writeBigInteger(final BigInteger value, final int size) throws ValueSerializationException { + requireNonNull(value); + checkBoundsFor(value, size); + + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, BigInteger.class.getSimpleName(), value); + + final byte bigIntegerLength = (byte) (Math.ceil(value.bitLength() / 8.0)); + + final byte[] byteArray = value.toByteArray(); + + int skipped = 0; + boolean skip = true; + for (byte b : byteArray) { + boolean signByte = b == (byte) 0x00; + if (skip && signByte) { + skipped++; + } else if (skip) { + skip = false; + } + } + + final byte[] bigIntegerBytes = Arrays.copyOfRange(byteArray, skipped, byteArray.length); + + if (this.buffer.order() == ByteOrder.LITTLE_ENDIAN) { + ByteUtils.reverse(bigIntegerBytes); + } + + put(bigIntegerLength); + put(bigIntegerBytes); + } + + /** + * Writes a String to the value byte buffer + * + * @param value String value to serialize + * error with input/output while reading the byte array + */ + public void writeString(final String value) { + LOGGER.debug(LOG_BUFFER_WRITE_TYPE_VALUE_MESSAGE_STRING, String.class.getSimpleName(), value); + + put(value.getBytes(StandardCharsets.UTF_8).length); + put(value.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Checks if the value is within valid bounds for given CLType + * + * @param value the value to check + * @param size the bit size to check against + * @throws ValueSerializationException exception holding information of failure to serialize a value + */ + private void checkBoundsFor(final BigInteger value, final int size) throws ValueSerializationException { + final BigInteger max; + if (size == 64) { + max = MAX_U64; + } else if (size == 128) { + max = MAX_U128; + } else if (size == 256) { + max = MAX_U256; + } else if (size == 512) { + max = MAX_U512; + } else { + throw new ValueSerializationException("Error checking numeric bounds", new NoSuchTypeException( + String.format("%s is not a numeric size with check bounds for serializing", size))); + } + + if (value.compareTo(max) > 0 || value.compareTo(ZERO) < 0) { + throw new ValueSerializationException(String.format(SERIALIZE_EXCEPTION_OUT_OF_BOUNDS_MESSAGE_STRING, + value, size)); + } + } + + /** + * Retrieves the backing buffer + * + * @return the Deserializer ByteBuffer + */ + public ByteBuffer getBuffer() { + return this.buffer; + } + + /** + * Gets the full byte array corresponding to serialized data + * + * @return the byte array serialized data + */ + public byte[] toByteArray() { + return Arrays.copyOfRange( + this.buffer.array(), + this.buffer.arrayOffset(), this.buffer.arrayOffset() + this.buffer.position() + ); + } + + private void put(final byte newByte) { + // check if it fits and increase buffer if needed + if (this.buffer.position() + 1 >= this.buffer.capacity()) { + increaseCapacity(1); + } + this.buffer.put(newByte); + } + + private void put(final short bytes) { + // check if it fits and increase buffer if needed + if (this.buffer.position() + 2 >= this.buffer.capacity()) { + increaseCapacity(2); + } + this.buffer.putShort(bytes); + } + + + private void put(final byte[] newBytes) { + // check if it fits and increase buffer if needed + if (this.buffer.position() + newBytes.length >= this.buffer.capacity()) { + increaseCapacity(newBytes.length); + } + this.buffer.put(newBytes); + } + + private void put(final float bytes) { + // check if it fits and increase buffer if needed + if (this.buffer.position() + 4 >= this.buffer.capacity()) { + increaseCapacity(4); + } + this.buffer.putFloat(bytes); + } + + private void put(final double bytes) { + // check if it fits and increase buffer if needed + if (this.buffer.position() + 8 >= this.buffer.capacity()) { + increaseCapacity(8); + } + this.buffer.putDouble(bytes); + } + + private void put(final int bytes) { + // check if it fits and increase buffer if needed + if (this.buffer.position() + 4 >= this.buffer.capacity()) { + increaseCapacity(4); + } + this.buffer.putInt(bytes); + } + + private void put(final long bytes) { + // check if it fits and increase buffer if needed + if (this.buffer.position() + 8 >= this.buffer.capacity()) { + increaseCapacity(8); + } + this.buffer.putLong(bytes); + } + + /** + * Increases the buffer size and replaces with the new capacity buffer including current buffer data + * + * @param increaseByteCount the byte count to increase + * @throws IllegalArgumentException if the parameter is invalid + */ + protected void increaseCapacity(final int increaseByteCount) throws IllegalArgumentException { + if (this.buffer == null) { + throw new IllegalArgumentException("Buffer is null"); + } + + if (increaseByteCount < 0) { + throw new IllegalArgumentException("Size cannot be less than 0"); + } + + int newCapacity = this.buffer.capacity() + increaseByteCount; + ByteBuffer newBuffer = ByteBuffer.allocate(newCapacity); + this.buffer.flip(); + newBuffer.order(this.buffer.order()); + newBuffer.put(this.buffer); + this.buffer = newBuffer; + } +} diff --git a/src/main/java/dev/oak3/sbs4j/exception/NoSuchTypeException.java b/src/main/java/dev/oak3/sbs4j/exception/NoSuchTypeException.java new file mode 100644 index 000000000..7d1322133 --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/exception/NoSuchTypeException.java @@ -0,0 +1,12 @@ +package dev.oak3.sbs4j.exception; + +/** + * Thrown in case of a type which does not exist being requested + * + * @since 0.1.0 + */ +public class NoSuchTypeException extends Exception { + public NoSuchTypeException(String message) { + super(message); + } +} diff --git a/src/main/java/dev/oak3/sbs4j/exception/ValueDeserializationException.java b/src/main/java/dev/oak3/sbs4j/exception/ValueDeserializationException.java new file mode 100644 index 000000000..8466d8272 --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/exception/ValueDeserializationException.java @@ -0,0 +1,16 @@ +package dev.oak3.sbs4j.exception; + +/** + * Thrown when type could not be deserialized + * + * @since 0.1.0 + */ +public class ValueDeserializationException extends Exception { + public ValueDeserializationException(String message) { + super(message); + } + + public ValueDeserializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/dev/oak3/sbs4j/exception/ValueSerializationException.java b/src/main/java/dev/oak3/sbs4j/exception/ValueSerializationException.java new file mode 100644 index 000000000..94eb1ba7a --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/exception/ValueSerializationException.java @@ -0,0 +1,16 @@ +package dev.oak3.sbs4j.exception; + +/** + * Thrown when type could not be serialized + * + * @since 0.1.0 + */ +public class ValueSerializationException extends Exception { + public ValueSerializationException(String message) { + super(message); + } + + public ValueSerializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/dev/oak3/sbs4j/interfaces/DeserializableObject.java b/src/main/java/dev/oak3/sbs4j/interfaces/DeserializableObject.java new file mode 100644 index 000000000..c4707f423 --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/interfaces/DeserializableObject.java @@ -0,0 +1,19 @@ +package dev.oak3.sbs4j.interfaces; + +import dev.oak3.sbs4j.DeserializerBuffer; +import dev.oak3.sbs4j.exception.ValueDeserializationException; + +/** + * Defines an object as being capable of deserializing with {@link DeserializerBuffer} + * + * @since 0.1.0 + */ +public interface DeserializableObject { + /** + * Called when the object's values must be deserialized + * + * @param deserializerBuffer the deserializer buffer to be used + * @throws ValueDeserializationException exception holding information of failure to deserialize a value + */ + void deserialize(DeserializerBuffer deserializerBuffer) throws ValueDeserializationException; +} diff --git a/src/main/java/dev/oak3/sbs4j/interfaces/SerializableObject.java b/src/main/java/dev/oak3/sbs4j/interfaces/SerializableObject.java new file mode 100644 index 000000000..4dce8bbc7 --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/interfaces/SerializableObject.java @@ -0,0 +1,19 @@ +package dev.oak3.sbs4j.interfaces; + +import dev.oak3.sbs4j.SerializerBuffer; +import dev.oak3.sbs4j.exception.ValueSerializationException; + +/** + * Defines an object as being capable of deserializing with {@link SerializerBuffer} + * + * @since 0.1.0 + */ +public interface SerializableObject { + /** + * Called when the object's values must be serialized + * + * @param serializerBuffer the serializer buffer to be used + * @throws ValueSerializationException exception holding information of failure to serialize a value + */ + void serialize(SerializerBuffer serializerBuffer) throws ValueSerializationException; +} diff --git a/src/main/java/dev/oak3/sbs4j/util/ByteUtils.java b/src/main/java/dev/oak3/sbs4j/util/ByteUtils.java new file mode 100644 index 000000000..e31c5f475 --- /dev/null +++ b/src/main/java/dev/oak3/sbs4j/util/ByteUtils.java @@ -0,0 +1,124 @@ +package dev.oak3.sbs4j.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utilities class for Byte related operations + * + * @since 0.1.0 + */ +public final class ByteUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(ByteUtils.class); + + /** + * Hex string code case selector + */ + public enum HexCase { + LOWER("0123456789abcdef".toCharArray()), + UPPER("0123456789ABCDEF".toCharArray()); + + private final char[] hexCodes; + + public char[] getHexCodes() { + return this.hexCodes; + } + + HexCase(char[] hexCodes) { + this.hexCodes = hexCodes; + } + } + + /** + * Reverses a byte array, used to change endian + * + * @param bytes array of bytes to reverse + */ + public static void reverse(byte[] bytes) { + reverse(bytes, 0, bytes.length - 1); + } + + /** + * Reverses a byte array, used to change endian + * + * @param bytes array of bytes to reverse + * @param from first byte position to swap + * @param to last byte position to swap + */ + public static void reverse(byte[] bytes, int from, int to) { + LOGGER.debug("Reversing {}", bytes); + for (int i = from; i < Math.ceil((to - from) / 2f) + from; i++) { + byte temp = bytes[i]; + bytes[i] = bytes[to - i + from]; + bytes[to - i + from] = temp; + } + LOGGER.debug("Reversed {}", bytes); + } + + /** + * Parses a hex string to byte array + * + * @param hexString the hex string to parse + * @return the encoded byte array + * @throws IllegalArgumentException thrown if hexString is invalid + */ + public static byte[] parseHexString(String hexString) throws IllegalArgumentException { + final int len = hexString.length(); + + // "111" is not a valid hex encoding. + if (len % 2 != 0) + throw new IllegalArgumentException("hexBinary needs to be even-length: " + hexString); + + byte[] out = new byte[len / 2]; + + for (int i = 0; i < len; i += 2) { + int h = hexToByte(hexString.charAt(i)); + int l = hexToByte(hexString.charAt(i + 1)); + if (h == -1 || l == -1) + throw new IllegalArgumentException("contains illegal character for hexBinary: " + hexString); + + out[i / 2] = (byte) (h * 16 + l); + } + + return out; + } + + /** + * Converts hex characters to int value of byte + * + * @param charToConvert the char to convert to byte + * @return the int value of the converted byte + */ + private static int hexToByte(char charToConvert) { + if ('0' <= charToConvert && charToConvert <= '9') return charToConvert - '0'; + if ('A' <= charToConvert && charToConvert <= 'F') return charToConvert - 'A' + 10; + if ('a' <= charToConvert && charToConvert <= 'f') return charToConvert - 'a' + 10; + return -1; + } + + /** + * Encodes a byte array to an hex string in lower case as default + * + * @param byteArray the byte array to encode + * @return the encoded hex string + */ + public static String encodeHexString(byte[] byteArray) { + return encodeHexString(byteArray, HexCase.LOWER); + } + + /** + * Encodes a byte array to an hex string + * + * @param byteArray the byte array to encode + * @param hexCase upper or lower hex code case to use + * @return the encoded hex string + */ + public static String encodeHexString(byte[] byteArray, HexCase hexCase) { + StringBuilder r = new StringBuilder(byteArray.length * 2); + for (byte b : byteArray) { + r.append(hexCase.getHexCodes()[(b >> 4) & 0xF]); + r.append(hexCase.getHexCodes()[(b & 0xF)]); + } + return r.toString(); + } +} diff --git a/src/test/java/com/casper/sdk/model/entity/StateGetEntityTest.java b/src/test/java/com/casper/sdk/model/entity/StateGetEntityTest.java index 3747be61d..41c833f99 100644 --- a/src/test/java/com/casper/sdk/model/entity/StateGetEntityTest.java +++ b/src/test/java/com/casper/sdk/model/entity/StateGetEntityTest.java @@ -88,9 +88,8 @@ void validateGetStateEntityActualSmartContractCctlReturnedResult() throws IOExce assertThat(contract.getEntryPoints().size(), is(15)); assertThat(contract.getNamedKeys().get(0).getName(), is("allowances")); - assertThat(contract.getNamedKeys().get(0).getKey(), is("uref-5e1239586b122bfe8ec9e3285f375d060ffbf90599fade7e807027fd228275cf-007")); + assertThat(contract.getNamedKeys().get(0).getKey().toString(), is("uref-5e1239586b122bfe8ec9e3285f375d060ffbf90599fade7e807027fd228275cf-007")); assertThat(contract.getEntryPoints().get(14).getV1().getName(), is("balance_of")); - } } diff --git a/src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java b/src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java new file mode 100644 index 000000000..0f37c0545 --- /dev/null +++ b/src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java @@ -0,0 +1,90 @@ +package com.casper.sdk.model.transaction.field; + +import dev.oak3.sbs4j.DeserializerBuffer; +import dev.oak3.sbs4j.SerializerBuffer; +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.exception.ValueSerializationException; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author ian@meywood.com + */ +class CalltableSerializationEnvelopeBuilderTest { + + @Test + void serializeBytes() throws ValueSerializationException, ValueDeserializationException { + + final byte[] expected = { + 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x5, 0x0, 0x0, + 0x0, 0x7, 0x0, 0x0, 0x0, (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x2b, 0x2 + }; + + final byte[] fieldZeroVal = new byte[]{(byte) 254}; + + SerializerBuffer ser = new SerializerBuffer(); + ser.writeU32(4294967295L); + final byte[] fieldOneVal = ser.toByteArray(); + + ser = new SerializerBuffer(); + ser.writeU16((short) 555); + final byte[] fieldTwoVal = ser.toByteArray(); + + final CalltableSerializationEnvelopeBuilder builder = new CalltableSerializationEnvelopeBuilder(); + builder.setExpectedFields(3); + builder.addFieldBytes(0, fieldZeroVal); + builder.addFieldBytes(1, fieldOneVal); + builder.addFieldBytes(2, fieldTwoVal); + + builder.serialize(ser); + byte[] bytes = ser.toByteArray(); + + assertNotNull(bytes); + assertThat(bytes.length, is(33)); + assertThat(bytes, is(expected)); + + final CalltableSerializationEnvelopeBuilder deserializedBuilder = new CalltableSerializationEnvelopeBuilder(); + deserializedBuilder.deserialize(new DeserializerBuffer(bytes)); + + assertThat(deserializedBuilder.getFieldBytes(0), is(fieldZeroVal)); + assertThat(deserializedBuilder.getFieldBytes(1), is(fieldOneVal)); + assertThat(deserializedBuilder.getFieldBytes(2), is(fieldTwoVal)); + } + + @Test + void serializeValues() throws ValueSerializationException, ValueDeserializationException { + + final byte[] expected = { + 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x5, 0x0, 0x0, + 0x0, 0x7, 0x0, 0x0, 0x0, (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x2b, 0x2 + }; + + final byte fieldZeroVal = (byte) 254; + final long fieldOneVal = 4294967295L; + final short fieldTwoVal = 555; + + final CalltableSerializationEnvelopeBuilder builder = new CalltableSerializationEnvelopeBuilder(); + builder.setExpectedFields(3); + builder.addField(0, fieldZeroVal); + builder.addField(1, (int) fieldOneVal); + builder.addField(2, fieldTwoVal); + + SerializerBuffer ser = new SerializerBuffer(); + builder.serialize(ser); + byte[] bytes = ser.toByteArray(); + + assertNotNull(bytes); + assertThat(bytes.length, is(33)); + assertThat(bytes, is(expected)); + + final CalltableSerializationEnvelopeBuilder deserializedBuilder = new CalltableSerializationEnvelopeBuilder(); + deserializedBuilder.deserialize(new DeserializerBuffer(bytes)); + + assertThat(deserializedBuilder.getFieldValue(0, Byte.class), is(fieldZeroVal)); + assertThat(deserializedBuilder.getFieldValue(1, Long.class), is(fieldOneVal)); + assertThat(deserializedBuilder.getFieldValue(2, Short.class), is(fieldTwoVal)); + } +} diff --git a/src/test/java/com/casper/sdk/service/TransactionTests.java b/src/test/java/com/casper/sdk/service/TransactionTests.java index 46b0b9447..16ebdf7f5 100644 --- a/src/test/java/com/casper/sdk/service/TransactionTests.java +++ b/src/test/java/com/casper/sdk/service/TransactionTests.java @@ -175,10 +175,8 @@ void chainPutContractCep18() throws IOException, ValueSerializationException, UR assertThat(((ExecutionResultV2) transactionResult.getExecutionInfo().getExecutionResult()).getErrorMessage(), is(nullValue())); //Tests for the returned getTransaction Entities/Kinds/Entries are in EffectsTest and CasperServiceTests - } - private GetTransactionResult waitForTransaction(final TransactionHash hash, final CasperService casperService) throws TimeoutException { final long timeout = 300 * 1000L; @@ -202,11 +200,6 @@ private GetTransactionResult waitForTransaction(final TransactionHash hash, fina } } - return result; - } - - - } diff --git a/src/test/java/dev/oak3/sbs4j/DeserializerBufferTest.java b/src/test/java/dev/oak3/sbs4j/DeserializerBufferTest.java new file mode 100644 index 000000000..32717e8ef --- /dev/null +++ b/src/test/java/dev/oak3/sbs4j/DeserializerBufferTest.java @@ -0,0 +1,257 @@ +package dev.oak3.sbs4j; + +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.model.Point; +import dev.oak3.sbs4j.model.Polygon; +import dev.oak3.sbs4j.model.Sphere; +import dev.oak3.sbs4j.model.TestData; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.nio.ByteOrder; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +class DeserializerBufferTest { + private static final Logger LOGGER = LoggerFactory.getLogger(DeserializerBufferTest.class); + + @Test + void deserializePoint_should_match_expected_object() throws ValueDeserializationException { + byte[] serializedBytes = new byte[]{4, 0, 0, 0, 2, 0, 0, 0, 9, 0, 0, 0}; + + DeserializerBuffer deserializerBuffer = new DeserializerBuffer(serializedBytes); + + Point expected = new Point(4, 2, 9); + + Point point = new Point(); + point.deserialize(deserializerBuffer); + + assertEquals(expected, point); + } + + @Test + void deserializeCircle_should_match_expected_object() throws ValueDeserializationException { + byte[] serializedBytes = new byte[]{4, 0, 0, 0, 2, 0, 0, 0, 9, 0, 0, 0, 0, 0, -96, 64}; + + DeserializerBuffer deserializerBuffer = new DeserializerBuffer(serializedBytes); + + Sphere expected = new Sphere(new Point(4, 2, 9), 5.0f); + + Sphere sphere = new Sphere(); + sphere.deserialize(deserializerBuffer); + + assertEquals(expected, sphere); + } + + @Test + void deserializePolygon_should_match_expected_object() throws ValueDeserializationException { + byte[] serializedBytes = new byte[]{4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, 1, 0, 0, 0, 0, 0, 0, 0}; + + DeserializerBuffer deserializerBuffer = new DeserializerBuffer(serializedBytes); + + Polygon expected = new Polygon(); + + Point p1 = new Point(1, 1, 0); + Point p2 = new Point(1, -1, 0); + Point p3 = new Point(-1, -1, 0); + Point p4 = new Point(-1, 1, 0); + + expected.setVertices(Arrays.asList(p1, p2, p3, p4)); + + Polygon polygon = new Polygon(); + polygon.deserialize(deserializerBuffer); + + assertEquals(expected, polygon); + } + + @Test + void validateDeserialize_LITTLE_ENDIAN_with_SampleData() throws ValueDeserializationException { + for (TestData testData : TestData.SUCCESS_TEST_DATA) { + DeserializerBuffer deserializerBuffer = new DeserializerBuffer(testData.getByteValueLittleEndian()); + switch (testData.getName()) { + case TestData.BOOLEAN: + boolean valueBool = deserializerBuffer.readBool(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueBool, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueBool); + break; + case TestData.BYTE_ARRAY: + byte[] valueByteArray = deserializerBuffer.readByteArray(testData.getByteValueLittleEndian().length); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueByteArray, testData.getByteValueLittleEndianHex()); + assertArrayEquals((byte[]) testData.getValue(), valueByteArray); + break; + case TestData.U_8: + byte valueU8 = deserializerBuffer.readU8(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU8, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueU8); + break; + case TestData.U_16: + short valueU16 = deserializerBuffer.readU16(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU16, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueU16); + break; + case TestData.U_32: + long valueU32 = deserializerBuffer.readU32(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU32, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueU32); + break; + case TestData.F_32: + float valueF32 = deserializerBuffer.readF32(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueF32, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueF32); + break; + case TestData.F_64: + double valueF64 = deserializerBuffer.readF64(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueF64, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueF64); + break; + case TestData.U_64: + BigInteger valueU64 = deserializerBuffer.readU64(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU64, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueU64); + break; + case TestData.I_32: + int valueI32 = deserializerBuffer.readI32(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueI32, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueI32); + break; + case TestData.I_64: + long valueI64 = deserializerBuffer.readI64(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueI64, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueI64); + break; + case TestData.U_128: + BigInteger valueU128 = deserializerBuffer.readU128(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU128, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueU128); + break; + case TestData.U_256: + BigInteger valueU256 = deserializerBuffer.readU256(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU256, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueU256); + break; + case TestData.U_512: + BigInteger valueU512 = deserializerBuffer.readU512(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU512, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueU512); + break; + case TestData.STRING: + String valueString = deserializerBuffer.readString(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueString, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueString); + break; + } + } + } + + @Test + void validateDeserialize_BIG_ENDIAN_with_SampleData() throws ValueDeserializationException { + for (TestData testData : TestData.SUCCESS_TEST_DATA) { + DeserializerBuffer deserializerBuffer; + switch (testData.getName()) { + case TestData.BOOLEAN: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian()); + boolean valueBool = deserializerBuffer.readBool(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueBool, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueBool); + break; + case TestData.BYTE_ARRAY: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + byte[] valueByteArray = deserializerBuffer.readByteArray(testData.getByteValueBigEndian().length); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueByteArray, testData.getByteValueBigEndianHex()); + assertArrayEquals((byte[]) testData.getValue(), valueByteArray); + break; + case TestData.U_8: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + byte valueU8 = deserializerBuffer.readU8(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU8, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueU8); + break; + case TestData.U_32: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + long valueU32 = deserializerBuffer.readU32(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU32, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueU32); + break; + case TestData.F_32: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + float valueF32 = deserializerBuffer.readF32(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueF32, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueF32); + break; + case TestData.F_64: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + double valueF64 = deserializerBuffer.readF64(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueF64, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueF64); + break; + case TestData.U_64: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + BigInteger valueU64 = deserializerBuffer.readU64(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU64, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueU64); + break; + case TestData.I_32: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + int valueI32 = deserializerBuffer.readI32(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueI32, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueI32); + break; + case TestData.I_64: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + long valueI64 = deserializerBuffer.readI64(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueI64, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueI64); + break; + case TestData.U_128: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + BigInteger valueU128 = deserializerBuffer.readU128(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU128, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueU128); + break; + case TestData.U_256: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + BigInteger valueU256 = deserializerBuffer.readU256(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU256, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueU256); + break; + case TestData.U_512: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + BigInteger valueU512 = deserializerBuffer.readU512(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueU512, testData.getByteValueBigEndianHex()); + assertEquals(testData.getValue(), valueU512); + break; + case TestData.STRING: + deserializerBuffer = new DeserializerBuffer(testData.getByteValueBigEndian(), ByteOrder.BIG_ENDIAN); + String valueString = deserializerBuffer.readString(); + LOGGER.debug("Expected: {}, Actual: {} - Hex: {}", testData.getValue(), valueString, testData.getByteValueLittleEndianHex()); + assertEquals(testData.getValue(), valueString); + break; + } + } + } + + @Test + void dataWithWrongInputLength_should_throw_BufferUnderflowException() { + DeserializerBuffer deserializerBuffer1 = new DeserializerBuffer(""); + + assertThrows(ValueDeserializationException.class, deserializerBuffer1::readBool); + assertThrows(ValueDeserializationException.class, deserializerBuffer1::readU32); + assertThrows(ValueDeserializationException.class, deserializerBuffer1::readU64); + assertThrows(ValueDeserializationException.class, deserializerBuffer1::readU128); + assertThrows(ValueDeserializationException.class, deserializerBuffer1::readU256); + assertThrows(ValueDeserializationException.class, deserializerBuffer1::readU512); + assertThrows(ValueDeserializationException.class, deserializerBuffer1::readString); + assertThrows(ValueDeserializationException.class, () -> deserializerBuffer1.readByteArray(32)); + + DeserializerBuffer deserializerBuffer2 = new DeserializerBuffer("01"); + + assertThrows(ValueDeserializationException.class, deserializerBuffer2::readU512); + + DeserializerBuffer deserializerBuffer3 = new DeserializerBuffer("01"); + + assertThrows(ValueDeserializationException.class, deserializerBuffer3::readString); + } +} diff --git a/src/test/java/dev/oak3/sbs4j/SerializerBufferTest.java b/src/test/java/dev/oak3/sbs4j/SerializerBufferTest.java new file mode 100644 index 000000000..d94e96076 --- /dev/null +++ b/src/test/java/dev/oak3/sbs4j/SerializerBufferTest.java @@ -0,0 +1,298 @@ +package dev.oak3.sbs4j; + +import dev.oak3.sbs4j.exception.ValueSerializationException; +import dev.oak3.sbs4j.model.Point; +import dev.oak3.sbs4j.model.Polygon; +import dev.oak3.sbs4j.model.Sphere; +import dev.oak3.sbs4j.model.TestData; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.URISyntaxException; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.*; + +class SerializerBufferTest { + private static final Logger LOGGER = LoggerFactory.getLogger(SerializerBufferTest.class); + + @Test + void serializePoint_should_match_expected_array() { + byte[] expectedBytes = new byte[]{4, 0, 0, 0, 2, 0, 0, 0, 9, 0, 0, 0}; + + SerializerBuffer serializerBuffer = new SerializerBuffer(); + + Point point = new Point(4, 2, 9); + point.serialize(serializerBuffer); + + byte[] serializedBytes = serializerBuffer.toByteArray(); + + assertArrayEquals(expectedBytes, serializedBytes); + } + + @Test + void serializeCircle_should_match_expected_array() { + byte[] expectedBytes = new byte[]{4, 0, 0, 0, 2, 0, 0, 0, 9, 0, 0, 0, 0, 0, -96, 64}; + + SerializerBuffer serializerBuffer = new SerializerBuffer(); + + Point center = new Point(4, 2, 9); + Sphere sphere = new Sphere(center, 5.0f); + sphere.serialize(serializerBuffer); + + byte[] serializedBytes = serializerBuffer.toByteArray(); + + assertArrayEquals(expectedBytes, serializedBytes); + } + + @Test + void serializePolygon_should_match_expected_array() { + byte[] expectedBytes = new byte[]{4, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, -1, -1, 1, 0, 0, 0, 0, 0, 0, 0}; + + SerializerBuffer serializerBuffer = new SerializerBuffer(); + + Polygon square = new Polygon(); + + Point p1 = new Point(1, 1, 0); + Point p2 = new Point(1, -1, 0); + Point p3 = new Point(-1, -1, 0); + Point p4 = new Point(-1, 1, 0); + + square.setVertices(Arrays.asList(p1, p2, p3, p4)); + + square.serialize(serializerBuffer); + + byte[] serializedBytes = serializerBuffer.toByteArray(); + + assertArrayEquals(expectedBytes, serializedBytes); + } + + @Test + void validateSerialize_with_large_data_without_buffer_overflow() throws URISyntaxException, IOException { + Path imagePath = Paths.get(Objects.requireNonNull(getClass().getClassLoader().getResource("test_image.png")).toURI()); + final byte[] inputBytes = Files.readAllBytes(imagePath); + + SerializerBuffer serializerBuffer = new SerializerBuffer(); + assertDoesNotThrow(() -> serializerBuffer.writeI32(1)); + assertDoesNotThrow(() -> serializerBuffer.writeF32(2.0f)); + assertDoesNotThrow(() -> serializerBuffer.writeI64(3)); + assertDoesNotThrow(() -> serializerBuffer.writeF64(2.0)); + assertDoesNotThrow(() -> serializerBuffer.writeByteArray(inputBytes)); + assertDoesNotThrow(() -> serializerBuffer.writeString("Test string")); + } + + @Test + void validateSerialize_with_SampleData() throws ValueSerializationException { + for (TestData testData : TestData.SUCCESS_TEST_DATA) { + SerializerBuffer serializerBuffer = new SerializerBuffer(); + switch (testData.getName()) { + case TestData.BOOLEAN: + serializerBuffer.writeBool((Boolean) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Boolean.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.BYTE_ARRAY: + serializerBuffer.writeByteArray((byte[]) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", byte[].class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_8: + serializerBuffer.writeU8((Byte) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Byte.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_16: + serializerBuffer.writeU16((Short) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Short.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_32: + serializerBuffer.writeU32((Long) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Long.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.F_32: + serializerBuffer.writeF32((Float) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Float.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.F_64: + serializerBuffer.writeF64((Double) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Double.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_64: + serializerBuffer.writeU64((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.I_32: + serializerBuffer.writeI32((Integer) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Integer.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.I_64: + serializerBuffer.writeI64((Long) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Long.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_128: + serializerBuffer.writeU128((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_256: + serializerBuffer.writeU256((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_512: + serializerBuffer.writeU512((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + case TestData.STRING: + serializerBuffer.writeString((String) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", String.class.getSimpleName(), testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueLittleEndian(), serializerBuffer.toByteArray()); + break; + } + } + } + + @Test + void validateSerialize_BIG_ENDIAN_with_SampleData() throws ValueSerializationException { + for (TestData testData : TestData.SUCCESS_TEST_DATA) { + SerializerBuffer serializerBuffer = new SerializerBuffer(ByteOrder.BIG_ENDIAN); + switch (testData.getName()) { + case TestData.BOOLEAN: + serializerBuffer.writeBool((Boolean) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Boolean.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.BYTE_ARRAY: + serializerBuffer.writeByteArray((byte[]) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", byte[].class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_8: + serializerBuffer.writeU8((Byte) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Byte.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_32: + serializerBuffer.writeU32((Long) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Long.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.F_32: + serializerBuffer.writeF32((Float) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Float.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.F_64: + serializerBuffer.writeF64((Double) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Double.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_64: + serializerBuffer.writeU64((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.I_32: + serializerBuffer.writeI32((Integer) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Integer.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.I_64: + serializerBuffer.writeI64((Long) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", Long.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_128: + serializerBuffer.writeU128((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_256: + serializerBuffer.writeU256((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.U_512: + serializerBuffer.writeU512((BigInteger) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", BigInteger.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + case TestData.STRING: + serializerBuffer.writeString((String) testData.getValue()); + LOGGER.debug("{} | Expected: {} | Actual {}", String.class.getSimpleName(), testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + assertArrayEquals(testData.getByteValueBigEndian(), serializerBuffer.toByteArray()); + break; + } + } + } + + @Test + void numbersShouldBeInsideTheirTypeBounds() throws ValueSerializationException { + for (TestData testData : TestData.LAST_VALID_NUMBER_TEST_DATA) { + SerializerBuffer serializerBuffer = new SerializerBuffer(); + switch (testData.getName()) { + case TestData.U_64: + LOGGER.debug("Testing last valid number (value: {}) for {}", testData.getValue(), + "U64"); + serializerBuffer.writeU64((BigInteger) testData.getValue()); + assertNotNull(serializerBuffer.toByteArray()); + break; + case TestData.U_128: + LOGGER.debug("Testing last valid number (value: {}) for {}", testData.getValue(), + "U128"); + serializerBuffer.writeU128((BigInteger) testData.getValue()); + assertNotNull(serializerBuffer.toByteArray()); + break; + case TestData.U_256: + LOGGER.debug("Testing last valid number (value: {}) for {}", testData.getValue(), + "U256"); + serializerBuffer.writeU256((BigInteger) testData.getValue()); + assertNotNull(serializerBuffer.toByteArray()); + break; + case TestData.U_512: + LOGGER.debug("Testing last valid number (value: {}) for {}", testData.getValue(), + "U512"); + serializerBuffer.writeU512((BigInteger) testData.getValue()); + assertNotNull(serializerBuffer.toByteArray()); + break; + } + } + } + + @Test + void dataOutOfBounds_should_throw_ValueSerializationException() { + for (TestData testData : TestData.OUT_OF_BOUNDS_TEST_DATA) { + SerializerBuffer serializerBuffer = new SerializerBuffer(); + switch (testData.getName()) { + case TestData.U_64: + assertThrows(ValueSerializationException.class, () -> serializerBuffer.writeU64((BigInteger) testData.getValue())); + break; + case TestData.U_128: + assertThrows(ValueSerializationException.class, () -> serializerBuffer.writeU128((BigInteger) testData.getValue())); + break; + case TestData.U_256: + assertThrows(ValueSerializationException.class, () -> serializerBuffer.writeU256((BigInteger) testData.getValue())); + break; + case TestData.U_512: + assertThrows(ValueSerializationException.class, () -> serializerBuffer.writeU512((BigInteger) testData.getValue())); + break; + } + } + } +} diff --git a/src/test/java/dev/oak3/sbs4j/model/Point.java b/src/test/java/dev/oak3/sbs4j/model/Point.java new file mode 100644 index 000000000..8f93525e3 --- /dev/null +++ b/src/test/java/dev/oak3/sbs4j/model/Point.java @@ -0,0 +1,70 @@ +package dev.oak3.sbs4j.model; + +import dev.oak3.sbs4j.DeserializerBuffer; +import dev.oak3.sbs4j.SerializerBuffer; +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.interfaces.DeserializableObject; +import dev.oak3.sbs4j.interfaces.SerializableObject; + +public class Point implements SerializableObject, DeserializableObject { + private int x; + private int y; + private int z; + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public Point() { + } + + public Point(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public void serialize(SerializerBuffer serializer) { + // The written order... + serializer.writeI32(x); + serializer.writeI32(y); + serializer.writeI32(z); + } + + @Override + public void deserialize(DeserializerBuffer deserializer) throws ValueDeserializationException { + // ...defines the read order + this.x = deserializer.readI32(); + this.y = deserializer.readI32(); + this.z = deserializer.readI32(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Point point = (Point) o; + + if (x != point.x) return false; + if (y != point.y) return false; + return z == point.z; + } + + @Override + public int hashCode() { + int result = x; + result = 31 * result + y; + result = 31 * result + z; + return result; + } +} \ No newline at end of file diff --git a/src/test/java/dev/oak3/sbs4j/model/Polygon.java b/src/test/java/dev/oak3/sbs4j/model/Polygon.java new file mode 100644 index 000000000..96b01e850 --- /dev/null +++ b/src/test/java/dev/oak3/sbs4j/model/Polygon.java @@ -0,0 +1,64 @@ +package dev.oak3.sbs4j.model; + +import dev.oak3.sbs4j.DeserializerBuffer; +import dev.oak3.sbs4j.SerializerBuffer; +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.interfaces.DeserializableObject; +import dev.oak3.sbs4j.interfaces.SerializableObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class Polygon implements SerializableObject, DeserializableObject { + private List vertices; + + public List getVertices() { + return vertices; + } + + public void setVertices(List vertices) { + this.vertices = vertices; + } + + public Polygon() { + } + + public Polygon(List vertices) { + this.vertices = vertices; + } + + @Override + public void serialize(SerializerBuffer serializerBuffer) { + serializerBuffer.writeI32(vertices.size()); + for (Point vertex : vertices) { + vertex.serialize(serializerBuffer); + } + } + + @Override + public void deserialize(DeserializerBuffer deserializerBuffer) throws ValueDeserializationException { + int length = deserializerBuffer.readI32(); + this.vertices = new ArrayList<>(); + for (int i = 0; i < length; i++) { + Point point = new Point(); + point.deserialize(deserializerBuffer); + this.vertices.add(point); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Polygon polygon = (Polygon) o; + + return Objects.equals(vertices, polygon.vertices); + } + + @Override + public int hashCode() { + return vertices != null ? vertices.hashCode() : 0; + } +} diff --git a/src/test/java/dev/oak3/sbs4j/model/Sphere.java b/src/test/java/dev/oak3/sbs4j/model/Sphere.java new file mode 100644 index 000000000..a52be02e1 --- /dev/null +++ b/src/test/java/dev/oak3/sbs4j/model/Sphere.java @@ -0,0 +1,55 @@ +package dev.oak3.sbs4j.model; + +import dev.oak3.sbs4j.DeserializerBuffer; +import dev.oak3.sbs4j.SerializerBuffer; +import dev.oak3.sbs4j.exception.ValueDeserializationException; +import dev.oak3.sbs4j.interfaces.DeserializableObject; +import dev.oak3.sbs4j.interfaces.SerializableObject; + +import java.util.Objects; + +public class Sphere implements SerializableObject, DeserializableObject { + private Point center; + private float radius; + + public Sphere() { + } + + public Sphere(Point center, float radius) { + this.center = center; + this.radius = radius; + } + + @Override + public void serialize(SerializerBuffer serializerBuffer) { + // The written order... + this.center.serialize(serializerBuffer); + serializerBuffer.writeF32(this.radius); + } + + @Override + public void deserialize(DeserializerBuffer deserializerBuffer) throws ValueDeserializationException { + // ...defines the read order + this.center = new Point(); + this.center.deserialize(deserializerBuffer); + this.radius = deserializerBuffer.readF32(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Sphere sphere = (Sphere) o; + + if (Float.compare(sphere.radius, radius) != 0) return false; + return Objects.equals(center, sphere.center); + } + + @Override + public int hashCode() { + int result = center != null ? center.hashCode() : 0; + result = 31 * result + (radius != 0.0f ? Float.floatToIntBits(radius) : 0); + return result; + } +} \ No newline at end of file diff --git a/src/test/java/dev/oak3/sbs4j/model/TestData.java b/src/test/java/dev/oak3/sbs4j/model/TestData.java new file mode 100644 index 000000000..aa7c71a61 --- /dev/null +++ b/src/test/java/dev/oak3/sbs4j/model/TestData.java @@ -0,0 +1,155 @@ +package dev.oak3.sbs4j.model; + +import dev.oak3.sbs4j.SerializerBuffer; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; + +/** + * A container for test data for the Serializer/Deserializer + * + * @since 0.1.0 + */ +public class TestData { + + /* + * Type name constants + */ + public static final String BOOLEAN = "BOOLEAN"; + public static final String U_8 = "U8"; + public static final String BYTE_ARRAY = "BYTE_ARRAY"; + public static final String U_16 = "U16"; + public static final String U_32 = "U32"; + public static final String I_32 = "I32"; + public static final String F_32 = "F32"; + public static final String F_64 = "F64"; + public static final String I_64 = "I64"; + public static final String U_64 = "U64"; + public static final String U_128 = "U128"; + public static final String U_256 = "U256"; + public static final String U_512 = "U512"; + public static final String STRING = "STRING"; + + private final String name; + private final T value; + private final byte[] byteValueLittleEndian; + private final byte[] byteValueBigEndian; + + /** + * Gets the test data name + * + * @return the test data name + */ + public String getName() { + return name; + } + + /** + * Gets the test data value + * + * @return the test data value + */ + public T getValue() { + return value; + } + + /** + * Gets the test data byte value as Little Endian where applicable + * + * @return the test data byte value + */ + public byte[] getByteValueLittleEndian() { + return byteValueLittleEndian; + } + + /** + * Gets the test data byte value as Big Endian where applicable + * + * @return the test data byte value + */ + public byte[] getByteValueBigEndian() { + return byteValueBigEndian; + } + + /** + * Gets the test data byte value as hex string + * + * @return the test data byte value as hex string + */ + public String getByteValueLittleEndianHex() { + StringBuilder hex = new StringBuilder(); + for (byte b : byteValueLittleEndian) { + hex.append(String.format("%02x", b)); + } + return hex.toString(); + } + + /** + * Gets the test data byte value as hex string + * + * @return the test data byte value as hex string + */ + public String getByteValueBigEndianHex() { + StringBuilder hex = new StringBuilder(); + for (byte b : byteValueBigEndian) { + hex.append(String.format("%02x", b)); + } + return hex.toString(); + } + + /** + * Data with correct value/byteValue for loop testing serializer/deserializer + */ + public final static List> SUCCESS_TEST_DATA = Arrays.asList( + new TestData<>(BOOLEAN, true, new byte[]{1}, new byte[]{1}), + new TestData<>(U_8, Byte.valueOf("7"), new byte[]{7}, new byte[]{7}), + new TestData<>(BYTE_ARRAY, new byte[]{'s', 'b', 's', '4', 'j'}, new byte[]{115, 98, 115, 52, 106}, new byte[]{115, 98, 115, 52, 106}), + new TestData<>(U_16, (short) 7, new byte[]{7, 0,}, new byte[]{0, 7}), + new TestData<>(U_32, 7L, new byte[]{7, 0, 0, 0}, new byte[]{0, 0, 0, 7}), + new TestData<>(I_32, 7, new byte[]{7, 0, 0, 0}, new byte[]{0, 0, 0, 7}), + new TestData<>(F_32, (float) 5, new byte[]{0, 0, -96, 64}, new byte[]{64, -96, 0, 0}), + new TestData<>(F_64, (double) 5, new byte[]{0, 0, 0, 0, 0, 0, 20, 64}, new byte[]{64, 20, 0, 0, 0, 0, 0, 0}), + new TestData<>(I_64, 7L, new byte[]{7, 0, 0, 0, 0, 0, 0, 0}, new byte[]{0, 0, 0, 0, 0, 0, 0, 7}), + new TestData<>(U_64, new BigInteger("1024", 10), new byte[]{0, 4, 0, 0, 0, 0, 0, 0}, new byte[]{0, 0, 0, 0, 0, 0, 4, 0}), + new TestData<>(U_64, new BigInteger("18446744073709551615", 10), new byte[]{-1, -1, -1, -1, -1, -1, -1, -1}, new byte[]{-1, -1, -1, -1, -1, -1, -1, -1}), + new TestData<>(U_128, new BigInteger("7", 10), new byte[]{1, 7}, new byte[]{1, 7}), + new TestData<>(U_256, new BigInteger("1024", 10), new byte[]{2, 0, 4}, new byte[]{2, 4, 0}), + new TestData<>(U_512, new BigInteger("123456789101112131415", 10), new byte[]{9, 87, -1, 26, -38, -107, -97, 78, -79, 6}, new byte[]{9, 6, -79, 78, -97, -107, -38, 26, -1, 87}), + new TestData<>(U_512, new BigInteger("2500010000", 10), new byte[]{4, 16, 32, 3, -107}, new byte[]{4, -107, 3, 32, 16}), + new TestData<>(U_512, new BigInteger("0", 10), new byte[]{0}, new byte[]{0}), + new TestData<>(STRING, "the string", new byte[]{10, 0, 0, 0, 116, 104, 101, 32, 115, 116, 114, 105, 110, 103}, new byte[]{0, 0, 0, 10, 116, 104, 101, 32, 115, 116, 114, 105, 110, 103}), + new TestData<>(STRING, "Hello, World!", new byte[]{13, 0, 0, 0, 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}, new byte[]{0, 0, 0, 13, 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33})); + + /** + * Test data for ensuring numeric values would serialize until inside its valid bounds + */ + public final static List> LAST_VALID_NUMBER_TEST_DATA = Arrays.asList( + new TestData<>(U_64, SerializerBuffer.MAX_U64, null, null), + new TestData<>(U_128, SerializerBuffer.MAX_U128, null, null), + new TestData<>(U_256, SerializerBuffer.MAX_U256, null, null), + new TestData<>(U_512, SerializerBuffer.MAX_U512, null, null)); + + /** + * Test data for ensuring numeric values would throw while serializing outside its valid bounds + */ + public final static List> OUT_OF_BOUNDS_TEST_DATA = Arrays.asList( + new TestData<>(U_64, SerializerBuffer.MAX_U64.add(SerializerBuffer.ONE), null, null), + new TestData<>(U_128, SerializerBuffer.MAX_U128.add(SerializerBuffer.ONE), null, null), + new TestData<>(U_256, SerializerBuffer.MAX_U256.add(SerializerBuffer.ONE), null, null), + new TestData<>(U_512, SerializerBuffer.MAX_U512.add(SerializerBuffer.ONE), null, null)); + + /** + * Instantiates a TestData object + * + * @param name its identifier name + * @param value its value + * @param byteValueLittleEndian its corresponding value in bytes + */ + public TestData(String name, T value, byte[] byteValueLittleEndian, byte[] byteValueBigEndian) { + this.name = name; + this.value = value; + this.byteValueLittleEndian = byteValueLittleEndian; + this.byteValueBigEndian = byteValueBigEndian; + } +} diff --git a/src/test/java/dev/oak3/sbs4j/util/ByteUtilsTest.java b/src/test/java/dev/oak3/sbs4j/util/ByteUtilsTest.java new file mode 100644 index 000000000..285d02be2 --- /dev/null +++ b/src/test/java/dev/oak3/sbs4j/util/ByteUtilsTest.java @@ -0,0 +1,87 @@ +package dev.oak3.sbs4j.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class ByteUtilsTest { + @Test + void reverseEvenByteArray() { + byte[] expected = new byte[]{0, 2, 4, 6}; + byte[] input = new byte[]{6, 4, 2, 0}; + + ByteUtils.reverse(input); + + assertArrayEquals(expected, input); + } + + @Test + void reverseEvenByteArrayRange_from_1_to_2() { + byte[] expected = new byte[]{0, 4, 2, 6}; + byte[] input = new byte[]{0, 2, 4, 6}; + + ByteUtils.reverse(input, 1, 2); + + assertArrayEquals(expected, input); + } + + @Test + void reverseEvenByteArrayRange_from_1_to_3() { + byte[] expected = new byte[]{0, 6, 4, 2}; + byte[] input = new byte[]{0, 2, 4, 6}; + + ByteUtils.reverse(input, 1, 3); + + assertArrayEquals(expected, input); + } + + @Test + void reverseOddByteArray() { + byte[] expected = new byte[]{8, 6, 4, 2, 0}; + byte[] input = new byte[]{0, 2, 4, 6, 8}; + + ByteUtils.reverse(input); + + assertArrayEquals(expected, input); + } + + @Test + void reverseOddByteArrayRange_from_1_to_4() { + byte[] expected = new byte[]{0, 8, 6, 4, 2}; + byte[] input = new byte[]{0, 2, 4, 6, 8}; + + ByteUtils.reverse(input, 1, 4); + + assertArrayEquals(expected, input); + } + + @Test + void reverseOddByteArrayRange_from_0_to_4() { + byte[] expected = new byte[]{8, 6, 4, 2, 0, 10}; + byte[] input = new byte[]{0, 2, 4, 6, 8, 10}; + + ByteUtils.reverse(input, 0, 4); + + assertArrayEquals(expected, input); + } + + @Test + void reverseEmptyByteArray() { + byte[] expected = new byte[]{}; + byte[] input = new byte[]{}; + + ByteUtils.reverse(input); + + assertArrayEquals(expected, input); + } + + @Test + void reverseOneElementByteArray() { + byte[] expected = new byte[]{0}; + byte[] input = new byte[]{0}; + + ByteUtils.reverse(input); + + assertArrayEquals(expected, input); + } +} \ No newline at end of file From 38fad8829a19f0a45285639640ffe00f480e19ee Mon Sep 17 00:00:00 2001 From: meywood <105049338+meywood@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:28:40 +0000 Subject: [PATCH 2/2] issues/371 - Added JavaDoc to new classes --- ...CalltableSerializationEnvelopeBuilder.java | 29 ++++++++++++++++-- .../sdk/model/transaction/field/Field.java | 16 ++++++++++ ...tableSerializationEnvelopeBuilderTest.java | 2 ++ src/test/resources/test_image.png | Bin 0 -> 100287 bytes 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/test_image.png diff --git a/src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java b/src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java index 1e56c2c92..6fe3b927e 100644 --- a/src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java +++ b/src/main/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilder.java @@ -36,12 +36,18 @@ public class CalltableSerializationEnvelopeBuilder implements CasperSerializable /** The total size of all field values when serialized */ private long size = 0; + /** + * Add a field to the envelope. + * + * @param index the zero based index + * @param value the value to be serialized to value bytes of the field + */ public void addField(final int index, final T value) throws ValueSerializationException { this.addField(new Field(index, this.offset, value)); } /** - * Add a field to the envelope + * Add a field to the envelope. * * @param index the zero based index * @param value the bytes of the value to add @@ -51,6 +57,11 @@ public void addFieldBytes(final int index, final byte[] value) { this.addField(new Field((short) index, offset, value)); } + /** + * Adds a field to the envelope. + * + * @param field the field to add + */ public void addField(final Field field) { if (this.currentFieldIndex >= field.getIndex()) { throw new IllegalArgumentException("Field index must be greater than the previous field index"); @@ -66,12 +77,26 @@ public void addField(final Field field) { this.offset += field.getValue().length; } - + /** + * Obtains the fields and converts its byte value to the specified type. + * + * @param index the index of the field + * @param clazz the tye to convert the field value to + * @param the type to convert the field value to + * @return the field value as the specified type + * @throws ValueDeserializationException if the field value cannot be converted to the specified type + */ public T getFieldValue(final int index, final Class clazz) throws ValueDeserializationException { final Field field = this.fields.get(index); return field.getValue(clazz); } + /** + * Obtains the field's value bytes. + * + * @param index the index of the field + * @return the fields value bytes + */ public byte[] getFieldBytes(final int index) { return fields.get(index).getValue(); } diff --git a/src/main/java/com/casper/sdk/model/transaction/field/Field.java b/src/main/java/com/casper/sdk/model/transaction/field/Field.java index b02118d5c..34e40fe5a 100644 --- a/src/main/java/com/casper/sdk/model/transaction/field/Field.java +++ b/src/main/java/com/casper/sdk/model/transaction/field/Field.java @@ -32,6 +32,14 @@ public class Field implements CasperSerializableObject, DeserializableObject { /** The field value as bytes */ private byte[] value; + /** + * Constructs a field with the specified index, offset and value. + * + * @param index the index of the field + * @param offset the offset of the fields bytes within a CalltableSerializationEnvelope + * @param value the value of the field + * @throws ValueSerializationException if the value cannot be serialized + */ public Field(final int index, final long offset, final Object value) throws ValueSerializationException { final SerializerBuffer serializerBuffer = new SerializerBuffer(); @@ -75,6 +83,14 @@ public void deserialize(final DeserializerBuffer deser) throws ValueDeserializat this.offset = deser.readU32(); } + /** + * Obtains the fields value converted to the specified type. + * + * @param clazz the type to convert the value's bytes to + * @param the expected type + * @return the bytes converted to value of type clazz + * @throws ValueDeserializationException if the value cannot be converted to the specified type + */ @SuppressWarnings("unchecked") public T getValue(final Class clazz) throws ValueDeserializationException { final DeserializerBuffer deserializerBuffer = new DeserializerBuffer(this.value); diff --git a/src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java b/src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java index 0f37c0545..2a9704c82 100644 --- a/src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java +++ b/src/test/java/com/casper/sdk/model/transaction/field/CalltableSerializationEnvelopeBuilderTest.java @@ -11,6 +11,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; /** + * Unit tests for the {@link CalltableSerializationEnvelopeBuilder} + * * @author ian@meywood.com */ class CalltableSerializationEnvelopeBuilderTest { diff --git a/src/test/resources/test_image.png b/src/test/resources/test_image.png new file mode 100644 index 0000000000000000000000000000000000000000..bac647dc2d990a51a322a20b527874427be1ecf0 GIT binary patch literal 100287 zcmagGc|6o>_&#n)j?}0SYBX9bCCen4p@xpiA#t?WGWI3Mk{N5+Qz^!fB_ku1&|=Aw zJ*Jo$TO*VuL}n)YzK-?#yy=|JIp5#s`}+B#KN3Cn^W4jIU-xy-gFg*)c5K_bjf;zG zhwdL|FL814Qn8Wnzc~6jJr8rVg>|&!IL?c ziNK~#ryJJRPi>_q`JFq@s_-4V(C`zzZ4ow^=!}c{AVF5lXQ*4BUsIL&p(U{gxu>y+ zK|OH!=pKG@b)P7mFL!M!LS-_E>1W+rcphF+<|p|jWRnf$brG@NM}%rE3lb1KR$NaW4O}f({Nwwctxg_$ z##Z5CskTicgSoJ=a+Qx*rklihsAlm(I(!ty)hCXN$QeIw}*Ef7&yAp zFPCliIL6yziGM$iv?|4{v2Ta1kQHgC_m6y(H`v~)sFWEe>riY{joL1M-cXTip3r;0 zwoP@%=$_NP+bVcgbX`4&m$IAIx56$&4opP|$Mwib4RmUfsJ*yXpc;k5qUm1fMpL;e zm11v8SV7dK`WhQJKIfwnu|+ili6xq>`%#T@kPBfEW6Cb{2JXD?2fDPD{kHQlmB*&> zS}Ya%t-c4)Q;C0n6x?19HZiyR@ zy%9cVwBu{=IVGk2bOCYiseSp&p$Om9FI0MH(uSq}wXSA|mK(;Vhf*rD_6MWzYvPMt z%}lEoIg`hak%0SS7`@y|Td|gQ1DhGp=dz$8jRiGoI>KwyQ^3t};bstw;HJ?}4B2setbs zk*t{#-$t|f#%71qIuQ@q4;QYjYqg;-V0fx#c8pbRAvOlYrb6U6Z@U=33w*2C&rS03dGx}z6Y=Xu8rt4wl`#E zPEFeY%c`@^?Cf%qfiacQDn#38$U8-p_-jtS-t^8*v$zx2LapF-n&QfE$1y*&RGY?q zCQ^h@4I+&DVbdu?ZnaE@BSoG`hrcYD?sJajqU#mZ+ZiD@!VU(a(z?)mcS})q9@i`gWCJvfSObofwzaEt1k=GRCv6eoJsR0AoB) z58j!Ob^nR1eX)RY%IMxL#T({PYSGpj^QOlR6?t14P`;lRjjTpBg5)S{7yT;M`_{Z6 zMV|N`am6*y>|WcaJXsZwdy`t`OpFTYmS7~`exxCIk_pT?|uDpJ{YN`nA|ttIs$LbRt&$q!EGh+ zzGBDXa`TJ_m)FCU->lm|NWg`3It3PeA6BdDNe#>tl4X}Xy285!I^B5i20lA(cVSd; z;yQVr#;)NKg4FMAM@~ypJI+6Ig}2l(uI8e+{q+;FTC0LZa#5YDQosE#596e#cxL_C z&A)zQ+MDrL9Ox}qxX}CR!x!&+D=1%$N&O`TkD08-GtpdBjHXlYj#J-C+Aa~s>_;9y z+R@2Rc`&4LWa|7g0Z@XYZ5Ixhwf9hNuv9YRT77>yUMcC;M{$`OYr433#D8f1h)bai zu(K+A>(v6YV`gp|HW$^&G}FGz$eGbC!=4s5b)x#NF4_d{s9B5ZYa>Q#=PZm)nf^d^`h2K6Fq7nzTKJTYmiKDy_tP2*F5$0S+MCsm zZN2Ad)FsVTJ{h5+P*qWggy-kPJuuSLEdI*1E4)z$xwP{v)?b$~ve#s<9;)|LkDdfP z`9mFk+hity2BQJSjv*?o`Fk?Tiv_r8dF@hFfkjL?ke12b`F1!=UbZK0es_51`Gg0& z44a>?P2kcR8Oo9nj;S66poEf@vIeXAKE}+O#*O+MS(U`3hW`6YGBRP#MNDplWmn-n zE&XtcLpQ>^HCO3Jx|E>oG2EA~?RKLRN4AXS>?diNw=?cA6ADbo>A$u5&?LnFK=1ER zQ&>J>W`O}9skFh$>d7OvGtK!DrBiC*#XB2vL>e2faFMiTLzemEu{g?IanrkV>j z7NtbaBPg2nhWX&b1(Tse%BpRj$2R3YIm6rg=#~-(>AqN%^;O*SJr>{n7R{9_jmU7* zY8dr>>G73WwYjj`nZ0VpB{0A1UhSQe+Id^V!W(6+;W0-pyd3MmrGy|YSpkl1N5HSj z{-ArP{NnwgZ*31(w|kC-fFveGizM(ylSMUPWV|DMr*|l6pFLLU8v~`!2+b+o_8cqF zXs$Rk8;Z`6A5#7k_k@u1doMMjIZp7o)hk&)bZ0;Yo2LAOW89ZOESS!HIASRIXirmh z&5@xR@knKBR2SQcn#{t5cdHhbe$~ne7U}xYcLevt|+sh~yGM6?_W85nY{fiPn~UIKHF%q0)Fznea5{%E>rrQDbnln|g=A%k2Y zyJDKoBUY}w_ei}d@Rc6w8xq&jk0zm{Q_5NU>;o$f`kobpAX2EsZWn-R*u)W!52L9e zKa|I|iQ^V`^siE)%MFc}HA+)P*J~3dtzzg(q3a2AsjjYm-)^+`CbFQUpn zH{LJIBOk5escKyqY1SYu>EV)u%cpzGjrg%o(Jxazfh?vI!hG?#$9`eOerXME3Bz;X zQti%~atiKF1!O<3dtRU0ZXuAsGNjsLE8ZjbJokCH&EgXR5P`p6*{!@JYmKsd*KptS zRluKn3yQNQWpwJ2{?g>ObP3qLbCF-*W#Rs_YYASp)6HjE9;Q|4XbSM3#nkgoKcuLI zrk))4&pcLIH1U#XOAte+CV4;hu2rpd^}nDVk@qY52;JXo`juh#rw%eHhn%QH-b}It z|5+@zoqLRT=yi?X!kZ*|{$pH>M$Y93G!fm@qtxg76U(a@?KprRT9UDZ-4AeNC%)Cl zq$&*B-zBpP1W+&56ooTi2rsI8TrhuRi=x*TNv#?Db#C?)Z5%=MS@e;>y`KHf2P5b` zz7m`jtX+8FMN6i`YVV=Mi+_|HQTKlJ?bNFG?4HAn;5r3$r1~EKEcq{Y%l3hy&3#%b zIhMF&wrZSrA#Y6MwXiZRGO4K#Bo}3un9lh}ULtnMJ6qpn+bt!T{R%#PcrH2VUvGC= z4CL_-D0G^C&Q^j?=r!uq8OQP*Sfu8%H zmFL{0?1g1q*D)V?=9N~U{h4r z-jqXX4rBdFtYWLOub;zSL(ZE-y7-X1i#xT@zdh!s1MSm z^hI@+1I=iZsh&Qr$PLuAOX)(d83nW`LaejT5%mwgH@%o>j7!PVI|-{-kI{?- z$Z0QD^UZ^?(IZ`nZY^K2%B!I2d`pN@Q4k2`B9kiPcHbB^=Twm2&tBtX$k>>9bw2ip zDa;C*Pfiq8o)+ldv2q@xi9rWu(k?kK6Nk6tZ>Edmu=|@a)#>&-r2`bS|3^qk{hcmI zWKp}E+t(9gu?=#AkUbR1c2Q5@a04=ax2 zkxu!G7iVVvDAs!e6!AgOBBWXIIHRt#(*hT?V01x$PL`RP;h@TpHMlX2k7;mHdQgyq zn=88mIaSL6ItY>4?8^@KJgjrhcyxWYg_U0Xb9D0oHj{vDzaP7?CiF6c5N3g8bz2YH zf&yI2hAaO(+NnsjY|xXCEr0X@z0K3Ygf{i*ruDzSo_@GO*AW9)>YjJc?{Nupz1Iz8 z?8}LR_D{H!s$+$D3T{S1d{B4flzy#xwU?y0sqHaCL33;PR;Vpi#?_;A z8XT@~KUupq3_1b2D zFpm7xcCKh7950%g$>wBr4;AVn!yD0`RWk&zOwrci7>+&t;4>F}yX^z>MKe7G9WH;G zkyYF=o#^QOj1s$0B1$0AhxRGcd%ujmq#ah7qipcIOP5{6&w^dDj=QHNQw;SH z;dS%|qcWePR&j`%D;HO#hAN7ZW3|kUPLIbYWIz;DF&CoY?wx98T9Jn16v$5c1>O(G z${99RYvt`h1rl$a%1Df-F ztyZ6c->%LdMbzgY_LZmEv=gJ%uVBo zD+7tOtN%hR9NFUhvDhJZCARzd5TluS8Al=~A+L^3*+RA)vn`{Yp!wuHbQ#+Kby;VW zbn<4n)dsL|{IPso1^hUI_45EzdE|CoI8LkZbVgGE5!tVAj;W>Ev?V6;_Aff|+&o@( z*;LFdd=$Z=xe$xlz3=+7V{;F+AKv=o)V|fwYf<%qZiyG{;iFHzZ=z!w2D6BkmE9$@ zHth7L5{%_TjW>4_#dOS1!s4Pq+C-qx{79$Bea$k0a8ac|_{1V}nZFHiohCZgz6ylUZ z{|0nVq}!fCd50T0@Pnmn$9)h+-mvqTt%m>3UzYZ`VB&Bx3JB``>MSL|<4iSe$=~mH zP~k$Iy8XHw&F_=Zj`{IvKKfRU-10?^oP&kadkdFu_i%5++SqKZHS>sK9>5J*H9U`P zW%+n5bsW=d*`_2EUFZsKT?ycbNzZtGR@?(CU!^DhpK^y1MDg1ugpWUb+^v)di*oLn z0)UPdpCxEfUU}G`?awZZIAUEjyNcFR*Jnu4zU)`JiyI9faXbl=q<}`Bb9Nt9 zTs}8Ij0kDQ15e81+Qn}GOA;!%S&LF`=Zh0!hFmXi50GJk$BWn%Ze|5g1v9naMXNc& zM*b6OyFK|3%kaa`6U4s_ochg!?A7a@f5Go~U@$SuGsL24wIuptJ^#MrUEtV1-LS@;lwx<9wH#e^-S$4fJlR@>8iP;GGdHqPfuSp~e7j`huS z7$YC$F0cKSYIrsIo0VF9A8C39^9AH7k`JlZ#+!d->|M**CJE7Rg7lxRlzoEW=j;&yv$)orwU zw-s!C52rHtyUD(|+Pqp?Xk6T<@T72puyPyU{QwGD>f8Q7V2}@ZH~vyo8h|H<{I^xRs}#iEaRA0*WB9V~Om_~dXVuAe zCd`mW{YS?{l2jUz(j)m@-pLi|cf!6!vchG`gZ4s}y`{&)m8c$CENBElaF!aeSDR(+ z^}3l@I<#oFAWGE5trL?gZv7wdxvwVLkBk@ot?b zb-Nt1S0v}J(^CWOLE{SjpB3ct(B9gC`?8r6$0l#0lZT5-1z*!O-paJbHg3>ast`cZ zuPYAbBVKXQ~kzf%v;5ANGY zC--mu_Ob5of)~;da9)w|f~`%f%TYq1SyP6=mGWJUqWI@V!5OUkE7~(x9BFm)LTF3R zV6!JFkJ6Qi`7X>a6EZ~wX>I8xC>2VQvzcHk{$AyW?P9XYLS(YU!PKPh3`+_ppR-s! zn-V{4*r9o`;V*LUd#7#WMz?2M{x!jtog9s-QC({g9yb3)wHoqFq6f zBh?ihzUlewcwSsRprYN#kq`nyuOtgN3cP>gsYZ3yui!C(u!l}k5((66yR2NT;Bd{g zq3R&~;7ZD9wl2+PgK|SIIb}`l75~qdl&LJ@YgU|66`?5!Om%jz9apdnAqZQapdD$f zU%IivzZD~T?+SrP(Wy(;BmiOQ->! ziB_jNfVr-7Ak5WN;zgaR4}aj9ZV`0}QQVMyPq+v`EI!|A<{@-9aOMgw5OSc?Lz3*9bPhfTQx@|3ro7so5MqRrPjFHo*V;XdR9G-bq zX1(Bf8)?~i%ZW_NQ5_TwclK}FaK#`4ig>P;VR$3`Nw_}ZIBYOLBn`_=JF@`a=aRG6 zJ4%$_fb&$tKwsDC)nwVIj;pwA#+%@mNG$Re{>*ITBFVYb=%Qcc)$#c!9#p=qkOk&}x2tPj`OvCj=*xJt zU?4OvWGc^eHW&rT(j}&B=-S=~x|)zQzfjYOTJ*|ZuLsqIr$=J!9!4<-w~S6*x}|Q9 znLlN}6obwl`r0eVl!>`PRci2nK4FacN6};s5Jx#d8fUvA+6PAJF4>t9c3-!PZZYWi2 zJwW6Tj1_zOOjAKsvDh@bV?D6c{lr=LAyP5X>=|}oKaA- zcl&H~oEu<7;E}E%P869$<#lhC7QfV?6bdKaXBEq16bKkNNt9jst0?<&*2 z3-<9>3RSU$=o}IM{l^1qQBu=3->4`S{4$}m9G}J)yu~jZR7X+Ztya@! zn{@J+aQUv*MgRAr&7OfAMGx!uZv~)ORsMGlit+9dNjY!qc6<$t1E<3mgX`R^k?JAe zuQIAuEU?;&23kVIOnck)tYx1XEKDW!J^A%m@6NH^Ngaj_MT`4OkLhmjY>GfyM!f$n z+V!K|dL5&J@)MzdTyHS9$o-D9Vxp5v`4RpSIu$ za z8};@8+B0hUQ_7U~P7i=-zz7UPC2>T~XI-wS`inQQd$BmODyDa4s7*)csr;ImV);XB zy1?KE#(D8c@3tkWz_;gdRnzM=knZx5W-|?;}3&+yKClOaAO|JYIT+ z!M^qnMAIMm1<^>45K|Sh&XQny2%0<@=VwduD>wUw3;jZ&ekgY>^fIQee?@lEqbvM& zy)BW4XMmIl-{8S{Uk(1T|?g)0d-GEZX8>}?*qRwsl(QepNrC3DT=-$I0h{7*n z#Hv%bETz{K`sAm&ZjXV?+HB;QHZpNiQ`PC0UHm^{Ts}*~TIaK>34HfapaUEA`%qo? zv$|0`LkKXl818Z`&{TMCuK_Gynbt4ov?035#2n#lXa5_Zd2Il+z!3n@_Bz!@n-=o| zM(kK$|DU>oF6;?{PMV7yH3pZ@fo6Ibee!9C@JG<@rPhcow$d%`#;naFl=FlT7paL7WP$9N1Qasw7O(OOj8P=Xyx zcoI)QUUkGno)1tX|9!QLK zH3IRtMd;}N05hcIe{h-Uwydmm#_DC+Ym|QZqvvrklD>Aw_7rc8niDmJNUm%*Cs3Gon~y{wm(YDcpaP(BT+(5twDkCwBQpz&EiSJ%i zX=G}cXJ5Qf8M8+Jwu*5%0RxwZ>6$RO52aG>piJsT&(Gn?yI+?HFtu)_-ReGaEg%ph z`o5fR(NwVzz8_Ji)L*WQhjkEB4EsXI!Hjeb=rR%h&`~(kDwTNO_gz8P$p6A*pn z5`d=_CZ<^&YA;hirr`d;^iHTET^=FBc!7@S$sy%nuH@s42~R9`Vv`QsYck85F_Nk2 zR7||raX4Co<(~Q}O`jHOF}Iqw^y4EK4u1VmvyhfJSkmLZp~LXOd^K1VoA$F(%zf50 zH}w?<&%j=2<~}_haeB}EUVFejrFC#Zu4}Y{uK#(CczJ5V6No^9*kpa8T@!+S^FCR- zjh!DYKNM0cfgkxW(z1E)Ia$>JRrGZZRzG`9pKvL!NlO&=gZuHLjwn1~Dwe*u4=Abf z6hKKuGofmnbV3%4pN6VKcJZg}L**uneL33aRr(}&dz?yK(+fanp|c@6%STd;_NoN2 zh+7b#H-6X>rvi2K8${N~b@xQW1_%J{mH=`Qmm}&@v=oFO$t4C*Fb0%b{gVgIZK0bgTnIzpy74A4DE1Pm4tn8heMZ&&MxuNlYdlYvX7KsfMf6F|@ z1DCzvrPjBbGk@47i+qL^_5kP3n-gVe96VUHg}gH%#8c}1E%c;t-#ePlMbShar7sue zABldy`_Pl(vtdi>5YRy#O>A+cdXRh+&hgKd$j|>T=E%!*H4TnIbPp7FXVJ73963DV zM~rvrC{s^e3U3TpB!h-aOB#uFAFhwsjiHFP?dcsb;E*cxtd)hMkfn)c9ZzzM4%(4*o^v@Mpmo_%IZFFp0`jY6Vx`<*62fGN6?(Bx^l>$FKe z<9OdbN_l=#azaz!8*hK1$-A{?2R?=xJ&1bf`Lw}i8kF^^c>UN^Rrf<#;;`17^uZAE z1yWAlZyg)n1=tFJsy8Eq-9Y)&~eJicV6? zLdY9IQ5)s2-)_2PRJ9h+tq=v>?6Xmnv7xS-)zwgX645|7Jcs*3alwLTDCP!Q=!Vys z0l<+90gg$0h4pa0B;-v*XU?-G|4Zpp{#V8LO9doNf9aIoNu@jO!Czg10g8N#pr;tO4dzHhF;Mce_*O8ycWqR@xPQy4C*E zxjaFFi+U?X)*4VP;I-}MrJ2fgZk(In0^{>5n)&iqw+n4Tn1J{PcoK(sT<8o+A8WZ> z5(|q{VF`Vi;NDbO_5Dvs-(#KXinTV(2!xEcBlmmKj7PLEDfXq@M@YORw_EI?oRKPXTvJB}5makp={u`g9z z5WnMST7@FPEI2Vbci2nP%A5ECIHdKou=qF!K_CGFqU8NDfpo+ zmA#R~p73qIFu(>7_}3qPxN?grdkY8{oRG}Oi8Zk1^|)$&vdI(>B1LM>>XC0EpP89Z zuKS1Jc+O+D>tj!+;FdYpOUV~MLYP2jIx;F2jIY3iWwrVX6(!Iapo{{rJZFkWEmb1( zLEfXN09*5}Io(FYAw_FT|wIOj663{sMo{Ezg z4}0_OIz@^kC6ZzQgo9paeXMi}g*Gl&6)no&0|;L{Ej#kro1Bp5c`hjBhQ4}}Sv@*#}1a{v)1N2BqTf=)<6CvW6;FZ8M)OYPgm#WIodF}+0XwuDdU zj5j%?HG`tm;s*AilXXm3sqp9(>`NF=rIix85V!Ns7Vd|h`X=k(;b0{JTj@SLSZ2EY zfC?T~`iS>Drqc(_fKG~cGpT@6Hz)zh5d^DW*)t5MH7W>&wcP6R&a>;3rklG<6csMm zKw>gYf=bihGUq1Zco^3MKdUZ>BN9OPD=OF5EuARSOqa(`EKW}Me|guh?B+xcF*2cc zO;4Dcy?GPT%EpKxH^$`QuLRFBKQTW!;(yM(<<|Ux`)i@81skKKf6n|j zXTcZN2C*38o988OC9B-J^3C^oVtD&S5n{Y?XUyx5w{S@JA+ppWTh8ewv$$ zg^aK(TM7r5l)_d)g&NC#R^7aM?HqQD4_B9UTtMGiU9EuYu zS4@Aw%Xvmqa5mea0Ru`GqtP34rV8Ri~t)*;Qr%0DKF*w`a4zwj!o{Jiu$F8X$$z*eKt9AruRE;cF{z^aHIA8G?6zgMI|PtdXP1dSU}x zC9O^2j}p0qWY_m9Gh)Mr5VR!O?E&&{SQ*3DSQ)lhWeASMdmS}MVC?`5$wpwlHvai$ zIy8#g3TWf1y@0EbdCz0H#pvS$5&Uf5Q(O13lLy-n3tOPH0TbVF?7nFU_*T1$_|bs# z3?Ou*Fl4(F=^TK)rP{&q&L|=>ums-BfSAbY#vL@9O$Uiz3#^@uPE6~TQo~w-+H4IM z^Lf~NEa(jrHc5o&!S7V5j0L3$)s7hK~-(xv+0!>1eD0_zN+7HpAxp72Mrl z7Sn(Ky-1PjULNb6@BI)R_EK;^thLF%9ut)OqNl#_w~gX_Y{L!ydup)~P}ro}@*&)o z$Xo%?n^fcvO#s1}0cr&NX;hZ>xL`g`*@kF>IKIkcYZ0Sw@L^6EHan8n0>l<8$k|?o zOAJ{akov+RzbWc=P;+Wt_1>%gaR!n;=TR?oR=I#jm?N(Pu~m;y(&(O9XZ5YzPUGdsfd*Zt7-80RyBgx2s?)@P0evpY6JR)1ZYLHYNW zmZouAu%M>$=GW(s(}mXkCDpv@Hp z>|2L7<|yJ-yGeYNj~;#cPy?xV$-?=>=5Qv_xkzy*@u4B5;mn7C&qiV@| zF$tyIA0cc4wC~^-cDEsM;+eVyIWqU`W`(LDB}%6)kjkSTuL8;lNK)FqzIah!r|dkF zN&BZr$5BDwURq#ZK_iIK`(!y`%0nhnv)Y)t@tR`x2B?sj1V} z$rG@B1#>Ili;vieKjdH2Q}XA&3|ww5hor1PbS7&(dq5>a9}5Pf(*m_Z- z;*D(yJ_+P~;2Xcr`~c{D!I{ByBZrg)of#_D(3w>f0X6|=uJQURdby?ZAt{TaqM&9b z&{WKmTbRO!Q2Y@Tx}sQ;5DT-vHrd-FcH3x4!+w3sXjD9QLoz`~>+n8Oq}_Io_{w(n zhfk^Uu*}Ww1HSrE(bsh|)#W%r?^&OV!gkkwWoo`gRGt!f-vDyvPIMfY^JueZA{OBA zZ)c2dsY}n)wNVM>kRVPx4J(II1I270tHYoOTZ?8D!Aenpaq~tR5Sld`=Q+BiTDceN zP-fv12>)XuT~LStc4-kUos`9y$+CE%{p#TNc3fQ@q%k=jZp@v@>#{p7EA7`XqkV8l zdDSz~BKABYw|2F)x*_s%J0O_>I}Gj%_&LnX#7Dml)4dfEpln|;o4r)0&N`z@-B9g@ zfCU#&Q_i655l3mH+b;$0K+V!qYmC&eEv*NVzI7_5Q@Xi+fb|{&ihH6hidAF)aPD@f z1l6w9xu+f|w)Y465P&WKqYEhKgW!M00)(*2tC@K?PBj>m^1Om8f;W6Q&)vcT;#VD4 zWhU5?4k|#s;e|W5a5v_by8ik`jdy({|MpH|cxKL36ENKi*eL(XK;1t|iVXzvhs5{$ z7mW_3XL(jxn5$;YJ>8P;3iR=1{!0w_rUU&JKB`R>zYK)l;HN9#B8?!yHkSAU#C)JD zU4*nx3J&^ufsV0D+rzutj+`e0f(W3lPR-6SPwKAe^fL{}vuB}K7)lL6?g7g@o6n#$ zaLlYCJ9@Q3QvcfuS}Vsc_)l~4G!ThgDcI-nARyxVix+8|n@O}AZiQAuLoutuC3(v( z$Eh#>^9W*^8OqC(C;Pq)I?RPLFHi)G1b!R1)OUAKnsqdKWJJ2&2tPk#7}CBM5giJN z*YR5iwh%B^2uMDVhqmN%o)%vQ*y@)-50Kx|hT=N4D5!ps!U`j4t{H#2|68 z#OK0$@%QRw`eIma&#J0t4f{qYP{l7-r22q20WkNHD|mavWm2GD7QBHra0P4>$(v=~ z+L${i+el3AXjXi<6{FxXw7*aAVf99MgaJARadaNiD;Y>73*FNAZ+Pe>0hAT$%8-I@ zFwtG=R3b!s1G5Qh2a22MwN1YhbtA>(B*JlvYk6Rk1ZrM@V!+bCM#g0=D>gas2KT&P zui4Yb$lUsH#8zoZY5nVAYv5mmUOsp7Ep%ca;{pH%m{7KeJeMN`R)_X?+NXel!r~S4 z3azi-Q=``=QxY0FvL6O@7*~m2J_!RFSOx(3&3+~{{9i8s{OI=&;MjU6opx?S zbz_l19ut~HPyyr%UL2d*fmfOQ>O6HynyC!y)tonlzIV07W{KW#q{;qM>d`i#kcGq9 zIlW1ia(VNHkuLFGG0C4JBCBVk^SAqLLV{~zzd_NInDTe&WQwwpj>2|$3 zSUqBu)R=0@>lgyUUu(U2@WJo2I)Grd4#@c+ZtrV##S;F14hTNhY~+E^_fGI40ob)o#d*ewsKTO`c7*~}#!sM;1yvsvhRJvlm-2bi zQwyBjcN*+dh z{N*AdRA5g~Ov(@JXzPcgNsMlwp(Z%?q46#r5z=|q#wUklqAbqF%Wsbk`J7T!S zoHDF2Dy3y4+yam+g8B4OO*QB{3=oLmpxk!X=%65j<|+h7So?tL8VGE;y%+cc(RNly zWzR->j>kb)Td#6p#_8YES8IyB!$RPt_#@+XRe$dnQ9Vw*y#aBd`WyQ}7kt2eP`dK3 zCSFLG-e4mxPi)R1rLlLb2rP5)tL>-dZgKoHlPtggzPnDmvO9JV46mHae-OSlg;GC# z)oS{cW67WeL0T>n(2Zm#03XVB=M$MFGqV;NKpCMM)~V!=-X013(zDeD=->+Eo1@_h6vf%-ns6JNWsSkjJ z3<6>8zk2qpjh-F%t7k9LC_ZyO6XNtw2VxSAdW!RZ-sp!2Mf}^HvVzW6mF^u(5(4?4 z4D4fkp%U7i+B$!4yt&~7vY$P>=~RcYry7hGA4U`3!|&mk%B$UMNc#w22sPeIysV#~ zu?Oo%)2Ojry{zWPFGZ*yt7fEfRq637Cax@74o&Y zA#tB?hm7xc(FI=3aSZn@A!q8|(yc5X&S8Bk<7iDd8hVW+sPi6l$}JsEFoep3=e#5QIngOPoh%o$y)lC8$~_8 zJ;4!Uw4>(L&qJy;DtQin@y5ooj#0g6PnCfRxi!S|e6-%O!R+*8nShGH)2g_!)R0}_ zuI+70`)6CFAP(BR-;XYTtwMtZ97SbI;a?_Z~+QiE2@+MljvXauG3Rc4MEz>66(Qdm;3}nmIOB}dNM_r=Qn9BL zouui5Wj?_3ANxQZny_D%1(Ry=v9isk|HtCLn*Id(+}v)q9D13vvDf_0b{0Me6s8Fc zTC^Bo4iWGbwd&_J%bTvwHpo&o-k`%9$(&2_{`DjS|CeWR=ULs0W5x=#Hi7IzJMlMG z)2E$rKc*NL*4#$_R_06xXq*f$8cWoLY74;hc)> zEL|W~Dv(fYn3DjR1H=Tc0cbGM4IcvQ==bxa>S#9jdHl;FlS8Yo1OBk4pz3!_2QCa3 zmYffUi~yTAuPgaFByd{;XJS1(}Gq^SUuz z;umucxa)YY3^u>hMBOCfQDte!dx{7xG$3{fTC=I(5~fpamiW)YsqXECWJ8X}LeeE@ zPkG%fD=HyPwxHSjDbp~K_bc~oqE6GBno4M$CJ|!#xB+R|RySY3w79E-0c*PeM}rNZ ze>;=hDm|r>coSM)i!;>auL+VcfW~c_z&s@1CQb-)><&XLr<<@vT9;EAk$AfKi~V#Ou=~+I3*L(;?^R>WmG<$^v-! zAD@Azua~HFAs4e#z^PIJg|>Q}45tTxNySH~plN-%dmGjXZ7S1lAJ`XO*V(|`K3{4m z2>AO;;yRtO3fBP+2EJzG<3`?#D0w+|>9)7Htg?dBVDZNqu&OXCBmsEC^hPcxuqR`{ z$c~7ZDUT*3yst46!@kcE%}|6?-GFzZ4G%o=RQ;@i06G9~zU|?;F89Q9lbT-tssSke z{mdslM81kP;w~^yk(Q)>SCU_q>sI z1KrkP;q;$81eITWLSOZC;Zr#}?{bC%`+Cu;PicQ$$q7)AH$Qbn#TWy1L4>O&Pdn#b z$$=qZ`-GUIYn|Pt4TAtFSCGYXgXSZIO>6w9e(cLq24MCE?rS&DZyx9@L1D-Au17)S zIV1qE!H*9SRD-uy%wL3_&!(mSgtL3v;}a>=D1Q}$I9<<*$=5F6;W=PEgs5M>u_e64 zN3Zt?eS>BfDEk()F$Q@5`Ki2twcYsbb>);iXrKbhf7$0AaJs)gUB%5;DePfm zS=zDSga6lYYrNdjY|F?v`g?hU#usQyzpr0qVJg8DWSsMj_QLM_(xD?^`irQDyk|m?1c4x_lP6iOSUKiH{)@h{daDjB zr#RMLZPR=`@e!d1K0)CNn_V+IG(f`ODroWVrG^AMHd|$gCgosz!c7DKJDgKL zn9Dy0!$M2%n^bN!aPzL1E9Gqss&5Le?91q3H9aE(ZjL^w zEC>M{%uTfA>ZmB)rJ`c`2O{jK^|5D{M~-$**EVHi;=abg(x;4{$Dvl zQ~Wy>4-zj=0nwkiaoFbVC$sOoR(X+D+_rox3ndCRcU6y?(ZK|5AO9V@FRI2}Q7ACf zKdW@W#L2=+$%q)Mk{rxa(Y9zR4<@o;Mx&n^L>v+ZhS)B#gfAb1l}Umi$n1v*2lmlE z>ApKJw*(nHeS-xI@EFvRQN_{x7GSPyzaSRTP$_U0?uSaOJEJvX=hjomq~u~dQH>`f zz|KX93Qpz|cXPNDJh!Z(U#^qGD+?s+ojIkEqAS90!DpB-r?1PGTKujC@$wH_O`}1l z4$VVaea?v;V7Zb2Jec$4pkfeh-La5r6>M^zg#AX(iM&z1%ZYCis1>iY*l)51df*dI z0>QK&2@<%HY=)C2|1BC$BA4`pvjzIdrfR|w<`uYna20i67g$e`xBTDCQC=2AjU{W< zgr-)CnM+{m6(1iYz zj^272LoWf2p1xkG3s?k8SePXJ4OV}{ry@>|L*pl)ec610`4zxfZk+w4?JAd_;p@?* z_rrI0yKFAtc4`F+`3a(=1D*Cm+mCKK?z?V-o%Z-yNOxFr9xOVf*)u~xVSngW%*@*I zBrdQ3nQ5{y`uAH)YZkr><|7tlt@R+|B|5l!poxyR#aXL^T{GB=)OBIMj#K-Z{y&E& zkFMCvi+Oxg3%$>)8Gc~jKW7J;A1EOKT?O-Mq6vKG45SbfYR!Qr{z^BXiGPxl%@lX$7ID$&Ka%O^yj-T3SNKD&-~qUy~#U4ur}(cpbt!yAD(Jz@+X->xTM$5!L# zrmWCW!##Gl-9`{l(o=tkWUl-1SxldKg^KvGk&XlpuWE95SQ+3~fFbJje-aY6hEsY~ zbt3{F{3HJc&o2HTnD$AKkw6RPCi2_=VeHN0q29mv@f(E}gDA?_QmGV4GGnbIL?fvr zTT!M^jIm@-i#-$)1~*F*5elPhSq3p`?1}8t*vr2E&U;k%-tX`8{XKsExsT7|bFVpb zUgve5=RD8zy!dW&=7xC#VmfO|K^MP-qE{EJTU~8`TauAzTVm8Eh*3PC{lh*SY3YHE z&$(zLwWtDc7H55PIgV0+a`hu&(V&!8b5gvg6xQebwi!bb%6p^lw zjAtRRw`z~Yp z@+INJ`Y=dP(~6}kB!iW`V6`PGk9SW_`#e=U??Xl%l)3ibj^+;EkE zmA?VYP*+GLY+Wzkq(4C38DTp~GM{5k7j_o^b(wTuy(MDYzf$YFedYGRtcxIlo!mBa zU$+sSs6YyUQ+5OuhUfCmFSq)Xn$fbCr};O5Q?39ZKX*9qK;S))J8u(++@(r)M&1WO zkiYu(++Qocz?7Y;{qZb9`99YLbph0e0uryY76w=hY9jQf^hRF5{J^ZtPk(>>?<(No zyT4^nq`~8*m@ZOQ<=v!u#2#lphg?|shBoMKJnwAMf=i%TJL^fRMV$<*rB^93-DJze zgz2>8UNc4>xA3mWnjBdC2iq$>@;5E7``JDymGMY0Py`s;aV2L1blcDo=x%%RiUk+Bp#SD4W*lXA_L362A%7VH;^{)-uxS#C#V_~*Z!x!a z6J3hj7XYe0w<2FyH2uPSkgUJbB(oZG%G>>EtbA`tsuW)kD^)Kj>u4!?P;L}f$5)*f zrk@>O_ysqRE&!rncecFZ@qjXb<%s*$*de8lH~-jJjSxmX;l$>TZ=VB5=dXhFmdcnZ z!5WGUn{ZeNI^()=pI_?G{;v68isvJz`|eer6+(DHiv_}>O$lCAm;jhh+2L5J+m!c> zD1QD}CVtb}BlE)4c*x*0SS0gyAC3H2Q+e3A6S;+F4x%s`GL$)WEJWL3K_cAVvl%ugcF;?!722>|QaCI$T6RLoZl()AjDRwb*6r%$1}R}i%dUY>|SnM*L#P^=eEBFx^qE@ z&D971TyUo?4Yy9;d6Z*bp9K#RWiM>Z#E7CJ3!Zu;N3u%&Ck(2wRdp_Qpq(em+W=1h z;=0QI&ll{;I;m0)7VKD~>&%78=TeU??tH5*KH{VTb0tKZQ4T{?=m{77=uh6*#94T^ z+)?#A^X?bu;Uz`T}Imh%cMR>4)E*^z6(+gP?k!~V*kYHg<))E zEuMSTNJ>FKDe{6{ZiOTcbZ9Pi;Zp47NDw&u2T2dKr2V|2_T+Vz^@G$vIvbfV1mNR? zTp}GRj6ew>YI{I{8U3kuw?R8scA0W4)qS4Xws_|1WYm)hei3S{ z<9%avcnyBnIBB@}A-3-KsU8hd?k)fIjVty)e2Rman5A_aFdx>|X$=-!6@1X?qy^_*G!i;E<6wyg{L7jx4*vVY2Q3 z%d3>^r#5tRD#JX#zUbAXfACo-OR_gm%+QvxK;bage7CFSk_IPnk36ax3FT zC;xJ(;ey-KS{t{*_ATj7ERo3@!szdlbK4`^&)!!@s zTvJcfsVT7H%76}4##2RbVdHFto(&~0;}AFq-o-KM;@5zuR41P@JOI~`)3Db#$#u`oL|F(i5_;|TXorv+ zwJD>&X5}`b?HPw#)bBv^^mpROdMWDu@wabmf(Z-Tph;=xqw%MN|6C#1HLxT+DJqQrYfDpaXA>mJpYaek>DkzuY@G9^x z-jo#3vM!=EwbNi+PXC0A%de`2Iqp5#&g#?q*6As!5DaCL;o|%{W5x!vbN$Vk?v)Q4 zF7$dJ87`nM=Xa7`c`qNk3C+}X-)>oMJrWK*c#zjj!EI| zg%7o>S*}!co#*d`My#|CDt5Kty`%wHMua*hiVMo+UK+ye-DHm>%J|wCz)}v0Z1f6Q ze|vvmB_pE9bR7wmp1KjpZ4-ptR#STaKL6H*-&=k_vUeMjXZ_Ajeef9Q*Bo)rU4yLl zv3hMS43g;yT`J2f#3SmqDtHkIS}0V$Oza+C>}!iJ)bx$J;U&j}k#XX_QFhO-9xR6& z>R}=d?6G&8H4FyFiG1RCOX0ii!iN^5*)j_Rd{aNdYZyK@uq_y|OjHBn0@+-IsySF& z6{aM?yXajUb7!8(g&cjW*J)Dpx*d7C6H%}+G4KfMH-A%MQ+cru7C%2% zY`MvQyr$W4tDOhMqWDpcX-36qcroa0k?#wuIlUM*S!8yd%kBy7>vLpp@I$m z_OQ!0Sb3Oh7$k8+cOeL1UtkekWjS-BO?@P;NS6Ko_FXJz%<$@petkSp%fH-60{1~$ zg{L^S_@ecbSH7MJf^UzC(sk0&q9h}X_!}W(Y6VOX+AzUXG*e9`C%h&&jQic1$f~?e zZ1PTC4`0A&+?E=8Klg||e3K{#b5M*|!4!SM>&)V}f~*PhD(Nlu6Cu4fzeXm$)w#-_ ziZkE=w>}uT4Ia1k-3DLj7qse-i%d5+G)-qUuY)a-Nr&v4zVWP4j*+a$^HmGq!-w(N zQWe+TM29BL0BPWXr9K_1n!VfgvErvo>qvuVvvIjntK$CmJ)ssaC*uVJcqEuI#o)a_ zz%A9n%^c~nml_Bisj2jim!$yj-6aDAaHNLYqkh_^d#kH=4ke=aEE^V7YO&%46Skb6 z8-K($^lTk=503D-xfpAUk=+fuSqJBRZQWK1SYQSKk5IgTjX+BsiJDZ?qK)W_(uWTs zH{~g;*afPC!M^8$qfaVdjs2K{9M zKKlKm-E3D%E^SMbx_AC@<<`s4`@;K5h!jrNFq^;!!|U9cOc3!g-;^lmf+k8qk9rdO z?()6^wHA82WiRu;J?wBrBMZop_8CPqimk5{`vcl+R4 z$wUcE)LEwQTp<l?Hp^zOsQREm3%Xb0zeqbE-DNG9$FpxZP8@ z)(C>5+hvBZ?M{{^qWmym2IZOrzW;l*jf2p4)$Pi9Ri4!5Ew9@%t_nrIMY#7nRZ&sC zebWcut(e=TOAduPx;(1$-6;r6ET)c=o4|aO0?01AK|H*H?28d|d z5f<41wm@46DNyz~Z@*69_!>L&dE7>)_G9TAcL}l!<8mP{eew|B@&F07zwK(QhxL;Y9V&=)M&NlS?8fqMmPeTV ztTq0UXY8jM^_!8S)*o6bUJre5Z{K}YWeYM52k{pA=@R6`U`Ke4^cS)kleQ6cBBHeH zak~u;fdX`BM@cyW_StUwh7pS#5;@!IVLBde(|$#v$Qtic-2J9 zX`WPj_t4J|R9|0zi=badt`Qfp1HYcC_XK%b8{*ht^EVrf(bE5&kN=^)Q(r3`_#1<~ zoN2U4ht%GGYS&J|J886Rm-jtu1tdNV;}KI2&I#bE3y{+Njqz5Br51)c9-qz>QJKO7 zg?+kYa6FBvXnzH?*hHuyKQfvRxKZcwHCGnY0e_Q)EONV%hBoT(se$+T1xOhXuNr~z zwtNR~H^ocU=F8%E;qXvuvece0{Uv`yaxToyb}K6{ojDGFlOOxALw@gg0PkhA!;CEC zg|$!JH>F%PSyM(noAER)#ENwuDG;8Ja@NfX%z8}Dz8H~m<=ygI0>Kqprj zB;D}4Y$;D~Vpw^73A-OY#)cRfv^;gKqwDTM6|}>9Ogb!xeVah-?)yxVnm=+$slh(y z2@~hrqZr+f7x=5j@{O4KaU*yS$GYz%-8Z@7fIuL(Cm~$QcNlLscQBY1p&K+34v(0V zM(oAfPyftU8#Pym*r#2ein_zpvXA_AJps{@Ay~Ldve&BF4c&!&l3_!=3VpbVx0LVK z;79G_`+qr-^ePYymkA#pzuCl*dn8$`zQ!{AR5|*M17owcAc2y7m!4UqY4Vh1E{84CBoLQm?+B zMhO1rbu_tDWBb;8I#GcvxPw}(07(>mO6P(cc@V*w4OOQ-3cknJ;2-UFf_s?QtyeWO+QIgyFGZZ*6_{kS<5W+e$=DX1UFj|^A`~6vT zwW?Lpn?BM)KkdCt9jmC$MWzCVsoXi*s17@G7x~wei4D~Z)V_&DqCps9oj)T$54%rU?Dtpg7*f3A63cJRf@&_*X@Blhf>q0vYh(RqBBC6 zJwu7Ni;GNFVcAN|kZH8pH+PC9m-oUj(11(sHAI_Co*_jjtq z?jd;m=Q;E7$?SRYDJBK2{fW$XTg-Jm*|mS(b^Y%BK=dJ+LnyG3mCS)>AJR8gdK(%X z!Do*k!uiNDTgvcFOpYz=kRyu82#4@x>_n^M4~q>UbLo6A&p{qJ3OW&d0}*VI2?jR6 z*FxcDauwb8rN26TG23mmW!~ySKAafAY?775dcXI0lQ`iu$v@j^+f9Mv)KQfLHjGs^ z{%G@FLw+6l#V)xnu7t<1RDZ1++w{@dn|0{H>D~MOzfA@Trd`HfFzx{iOu|l9li;Z} zrmf-du*JuZVj+*RA!^vme+9YDyQlDApd3ZYK4UUZVRZRa_NiOjnNw-Jx0O@TJ`tO% z0N|;kUy3(HF)NDh0Af>l<&K`?7 zqz50vTr`9o7(IiMINz@3b-khnOxFg0*cG$r6x8M;3)bLicGYdyylO&H;a!_ln0UNL zFAG#7iYK6+!Rt3??4LTxfI!TIh={}8msdt6V}HE(xeUGaxwfg8=8e0?U`QRlckWaV zb6ea6fQS%kq~!e@uuTb}#HXHYk?@3eWXNO3Q_T-Zuy^JRduQDc(-9>^`%PDE760qn z4XqR0U*Yw;zCk3x6{0_K+4D(9@)8DuF6;f6jWhq zxsrnHZB8Q+9|shBjd4mJGY(yQ*NRZh*6R57w?MKY!a0wYC35>Geb{ zMW-FAJN?(z32c57;Ul_d$DV`_#xDI2^|butI|M3J9cF>DtLHb7F_yBT;ER@#B_V9fffXj#imprqb(GriPmw3x>)DKH*2W>f_&DXj1W@ zcAhWisk(+)2u1Uk->|da=B6!I*168!A4_#fFU9&wuk%+UWyvHBl(aOk;ZG+{PptRv zooqVzwOdtuE)-KPQ&qrA8S4-j9WPqMuraL7|L4!5O+7oefhL`9VL|`l&>wz{LDfA+ zl1d{!c{z8-L02XBj_q^p+S#BQLq{{Z*2-6xtMQ=%F=1J+GlNbmCJFak-Cx^e*;OFz zIDFV4q?9csF4>+lNR1?gGehZR^6L>QD`yu5Gu#@N+l)zj3Z@*yaHlnuO?+!*2?Y*B zM=|OE<_d+1VuVimI9yuRiONfk`I(hPOq}HLoOwD=m)M7kIj^YQw0LG^-H2CcM)Qr3 zw$ka8#W{y2o}V(^JhcV2E~GpW*lK{@kq7MSfX6Mr(b`T z%vLFei!uLW(bI)NqnQ^=IX0v{^Mg0kNYSCL^XNnMMt}XU19$wdFl-EPEX8 z@F-fySR9}&$*0uclkV$xI9AggYOEJb8>%y#m-b;->7O=A1%RtE0U; zD@ZRaQfjRk31XG$*?3nexp~j|0*Sg_9ODurp;j|5)Mc+o`=*@+;TG@rc!-)rafz@} zLu3x(PN$Tn&Q&`^3AnwF8rA>i?&hYw5cB5U)QCvZ*qb7`ll_gufs5nvM>PJHUWbJ< z=IGmC>|BO*s*>Rtk|7!E5w@d!CwDoGK6EYg3*cSHh{fo6JTg{i&v+-}z9NA8KMYPO zjmMV}G^KhtCZz^W2q|8hPO(1C9~B#3f6D8VQmE(B@@PU#i&?a%OOBoCle>KZ_CaN| zjSPd+&dyT6T2llBQl2+-HM&1^DEmR_c1TVdXxF~<Z7}~>Mjob_KrA{w@pkzGRx>=%z zXDAjU=HcJJPrlNKH)dRJG&fHS=2}?VziKcja zloB&nTnTvB&nC02vbLxY-hRI+99n&H*ORpGC64kwV z-71XF?l!TuT3+EQ-?q;kANp3=O7b~BBeyV}NsFYj%OZiDEb7~+hW0kT$(Hf1TSReD zAlxXVn{LJ7->aA;eS4JCrSOa~iCc55T#D=Nnl-e|Ro+_T*`}1hQkAYG5+xN_s z5w9PPu@JlJ8PvUc7t5OVUKT0ViGTNVTa3lI??cZfQTK1ojidjaAbh%qGH6+4A;%&H z_4XR5X`;W0dHf*S{+MFfyakU~AW*ijgY2i)+#yipwMi9~8ij#qfUA#6^~Lz8*87Zw z8kdU|@>2GQD28Z#JMEP@XSiI;Rvy(f`F`@sskRxzk4E|{{C#EEc3OOBXIMr}ydiv5 zdv)x-o2aZgy)&ff;G=bw^rV(#w~21YW}4esul}N;>o&o9&oaFpFUu}1($N$Dhs9EM z!|7QTXOdml+HVsh1fUoj`9oh|#6mm$6aOxPOBU*A@M)RHPt9sKVktNW35F7- zjTmq{_@4*s6!)YkeEGxd#9|a@99wXfw1?e-ETd^L%JJ5t&PT$d?*o-AjOO>wHN@q; zSE?HnBPWo01`Y;H@NUuAB6Kl_w&Jq9U%6!>E9-Eqys#N5j5+{d0uFgzz3qeUDqP{ zn5Sx27xKk=vT`Js1m?rtqO5z~U=t^J9v-2eYN1;MVx>hc`nIUqKfvT2DA;*Lgt~a5 z;>L@v{pTkx&G!YAnKBmUE>b%mVP7`2%$(AAS4saTQY%Xw%?4Wu!T&%OTXAL&Iq%un zl(}I-&nDkV9NOYG60Do>9f{LiLEzEg?#6~LMI_*6rv!r42BI*WxD2*$NEirs4(t5S zpP(?wq=|_vbPu;Cy2mDXWUgelp$Uo75Mk`@*KmzU-j33o>9fF=NByXCs1}S*P_yUJ zP$pBumdO)UDs9~TSNEq&Bu1+Zgko68%;eBuXC4xpY%k|vJ$jtf#N!wzVnk{>)|@tE zlaYCg6vj$xsqPM)U_;>bCTg=UM&cW|BDx7fv_2c-jyytTD_~!M7F7_t-dUnn{GIQ3VlvwKfDd1l^5n zsEG0KQxhflHGEy}8kmflm$GJ99;qu1Ib|#5nzGp1v!^h4IeF#yL7%bmfkPzzSvJbt zeiQeZ%dY5C&YT@jB&(nzlEdI+T6h@W=o&>|=1S#jcwVGr|Hm-3c-apY8p*~6s;(<_2je z5UxrorXpJIknf~pwgRX0biDj_H2c8I+^y%yxPoGOR$8)2v}b8=%PBMq)PZe*b%u-( zGCM7E{@cjcJ`b+~s?(#a3K^8Pbdp(4M~BxHX^MZ1<1Y+#`~VpG1m*?uZOi`nTvo3J zEdvJxq5J053(ncajB*v}1s_#@`8^c7Gx+E#k_3&n0`{+Z(vNqlKsFb{QmBFb<46}9 z6l3VB<95=-$$Pye#!byE!XIV6Ms2u+U{%o$P=s++5Q;5A`_Qg*_(e1MxY1~*=*kPsI21C_N7I|9~yCg9+L(4M~H^XS74s7bB)!K2_+eTPx2CBAIs(;kr$w3I2` zdSAML5xVTi8=9gw>J&d-SHfN5NF>cSPq>a^kER>ZM3U?CP8NNd&--(6>WY@qCi~|~ zgZ60rE!+go!P?j%aCgIQ@P-{JOGwctJ`m~+dtq@@((O3kbAf8EA@)_SJWzRU_H9%; z!aRH*ug-(+zI`vrTf>DIg)6EIvUS=qbQLROchLo#UmeM;~tGGt^ix=fGV;)GHP4&pK#iA$ev{QhcI!ZB*|3Wh>us}_8eA0@J>Vg9O!Kw#0d7(JJ zArMiPNg0m?2eSOO@@N3vMe{8tX=aaaIzM6U{0;yV3+CMxx9jA#)69sr_P%{-{_MPV zfBgjh$nEVI(oKd$mu^Sivsr3GzOZVrp2IH$oKUtfo4>q(r=`uw4h0>+5!3>)8)~i% zYZaH>rY=p~9V5kh1}_>#C#d*Y%#t^K+dFVc`vZkm+*fbI&5Xq0?)qolzkxy>e~yBx zJ)dq^f;jUZGc_(3mXplQSeSOMou_FHB58ZA{dv|qH!6uj#Mbx(kTjYD1+Zgp`Im^q6OW3Nff`bAN3O?1t<>mg1a*#kD-gD z%R-O?wwU$UCK1KV+jjPh6X_M@|MRCc|F{Bts=vLkuED+^Vo-va zsx`mqW_FS&$Ws6)tOQGUS(B|4{4-+QWPPmf%6k6?ghR(k=Vv-CH8XhSd?Cjiwi#_r zWjHXJ(G>$!N0WpeQhMl|7`1u>qwqQJx@vW#Wvg-t!d!WOpd&n_{{&Y4lt@Hpn@&%o z!?-mltIM zs5}g>WUn{ED>>g$cPlUf#O)V4++xJY!HuzHY$*e5J=a^a^c!w87!dhB#tqH;1rW23 z4*;}ECe1|S_@G9xIzEUT?)@M31>duE!xJZ6C-`SR?AVEu!yoA2OPQO|W7MgSg-ciJ z4L#wMPZ|1af0BT2qke--fc2NIm6BsUQ2i3O1y2|jTpll-#DtHYDL7zILml=l_KFvo z8%z*c232V+RNaV+YKW~}jbA^}%Y_vk(HCX0Av`I!Gwz+J@V`503Xg}Vp;=P#kcTtf}lsk21-q{uvDmxcqkW|Z~!8n9}XY_6P*gS5?DDis< zVklo<SuRZt#`wL^3^-)Wt#28g^M@4ole21vQW2< z#9}Hs&$l40OWzI@v`wL|o9If-U5fp#qdF4h4?>hD1|(_X6XGTb=;X>64Ry6rI7vADa3M)!A`1%z9Y^2eA$RTh z#L2m4&54Xup9I-o&EY$~-aQBrGIrCa@1>G2C$5e&nAdH%5prIi(n+*8Ga@)3B7$2N zxl=eGZi6q5HK8FS*4hKpY!EJfCZs!_vI#6aBdjTZrz4iDo~QrtLu@E`GnO$htdT_g ziML6>rC&Ii{RngRZg>?>4UW$im?*iD9#jB{(SNlLaoJ?>JbSz_>tiXF$$6Yn+Zw2z)x%{7A{i_*;Z6G zbj(KEE7(Q1V4s)ey*(7$T2aDFY!2DebpGIhk(Qql@_Q=Da)l~$k*~KfuaV!YRGQHv zCf3RSG7f*Buw~j{^j&rURaI*x%5!-lqzRPQ*pm-B1=$9`XFg^*-HP569CB_{i*g=J z%dv!>`$yqe{pz>~0imvy1AbGcbr6plqLFuy>W5_tiruy7i(J<(%vjJ;M%%lNlguqn z$^;K8EYR3TRKrZ`0G9>mTNy%(ZuK!MTm2yM3%YXb8L2e40ch?dJY>8E1Rw@CsYZ4y z;#1<=m!CXI4@t;*az7;5t2#akmL>?$r_T6zllKA8wb<~*hFYXN7e9mKB8_2V4Pb>> zApeLTQ7zwld~jO~+@Al2of>=-@2UNzE^{R;Yzol4y}vGkBl$#Ax5b9McKFdtk~E1A zIZzp55$d`Rqy36;Y5rWzaR{`-81nW2>aV`;@r1Hd_~?NOJyPPOLcucRG`*uMNZ>1k z;4&PB0J}#T;?N`bsDZZxu6p|Qs(~>Up`Ak*z!(8fsnvqVj+wi!N4f6)pFasqfKz;A zlMx5q8%qI_Qd#_Y`yCk5o&AjjfV~&p0B8jt{{Wi*eE_ro>KaCz`+f?c3L?AiLp1zx zL7W+e>kaK;H_ME`poOiV)zC>_O%Pd}`cJ^4o-ArXm)$faYy_N;k>3);N zG}TKXD~1lf@6FLNDc#TFVw~R)(1+ken$Z|lPNpqYXesS)aklym+gPwItG_qpkP*St zs`-6@vB$q{F@54m3h6(bw>cl{q2eJ{F2*+m9H|d*q~~07CU8KT^=*c1z7yxC=BgSY z)a%P&n=$+7D?hv6WYa9OXGXhlSEuvr9dyEo?WcBW_;LDX|pxX(1Cp6zh=hz{0710-6OX= z7T6hzuWTPCuzDTcU&&(^rxUp)hcfIi_pUiFPr4VFG>chW*1(H7F^`9JH^%9&Z{HGg zD}t!cd8)6};*VVmbfOq^L>3k{fWc>pd^-)YEN_z@8-vHixB6n%0*8A>Ir6#L_N4_2=VOq3iW|W-XzA;x`Xpnp3fn=`Qz0*=#C4gp%ufdgFVLX@| zs(r_AGCTZzQTIlU$$U)k1qaVZQW9S+yAf})P~~Wx6p@_fI4cjg>O;7Ds*v^htb?OP zXf3TIHZN2leQP$|T7gAQ4eAA6y985bK6+@U8Wi@YHN;~?zVlh{E<}>Z(YkTWF0!9~ zy2gIoXK$kBpcpPI&e8Oda5ZJ%_JsnaWUPLH_>$#2?ayS{1A`wZaV`y+^bZc1_cQy- zh|EGpJ*D-XUQi;3@1*BGMNZ#_m@_lU_vqK@vvaAFiOqRAD@n_y^G?<$O6Gp?pL)Q5 z`W;2$^w`Wz){~!q(4o3Fuk)U0I5Vs*PF%$9k29)@%Vcmg?jLyJkRj_XG3ZF)fSOed zsdFrX@Sf%92rDoj%Bb~?n?m#f;BZ;Okf?UCQ^kM?+t-vsy+)H@Sc?FTHRY!CGmfJ* zv*ILZ5P87P_HSE)-L5ZSFn|+qL5+OKE2z~+bLoY4vyu)wNQ;zi1w z1LWALx@uX{4{LwWLrF|)L! zzBdQh*fXURdh5tLMG;f{X;;Z5`zmh?x;QSD!Pd`2xg(;h05mg7k?~PXn$d5Qi54-Q zpS?Ce9IgA=Wly2@6(bcA5Y#R;j zOe7Hw#m$KYcekJek!CblzpVb+EGm^U-YZc41L0+lPT6P|vw1O5F4m~QbvE+)E>zOR z`t%igGMB!aNBk9u|HFOzzi?DbmUURT#9RaI(V5?SGGb{_C$he`%dKc1|9(tWfxQ0Z2|x zKQ;R;LalB{4M1DiJH+Q@r&mx?=arOpREY%n^|RrxPOerDaikk%94hrO|bAel(dQCe3#^z6lFKctTIs;IyWh(dW+rCb}z>&J{d5wqG$x zjV4IDF()h2NTOd8E1bc~Ed+c~h1P8jS~uVKs{QpM;x%G&8UTjPX{Y1W=66uf$|(*t zJD(V<1T3B=*8`&FqF`vlTa z&!@M{m*2YIUPIkzMO5^OX}~Y?pLJ-a)!9UQL2GF|nff>2D9kOH$Lyb2;Ia4miu)5O zoclp#RPs9++m8_kHPQ#VQHIhzepBj@jczsMnI;W{HSs7)1>ow81Bi$h^vF=fgc{53 zZC0DsJ+1M#rHsUe$AQBSv#?2-ufC(-34PRNy5i$6a#03Q=vEdbhEYVX%gf7qgS2Lh zw=M`FsW)Vp`2GD8DIfD%Cz`Bs6v1udKRIOheOR}pP3mkAYiy5FV8wc57NOTs6x6*y2Q+ibiCv7rz1Ty6Hb&q( zLeO0_E0-ZfSGzun*1e!HoWDmwj<`;t@9S~W3u7ou1ISg@QUuWo*Q(`I1QWVedCIIp z8DPxbZi%&czeD+PNdG41Q5`|oZVTPf9jFv}jZy85P7a@=w{cA`YB^WJ0PjY1efX$m zan{&b2PvJIJF}-&)8~uB_2z`*XqqA+$jW0|*-ZT}29mzOWFP~Go6b%gMM$Rdd0-M7 zzdpAu)U1<86L&J^i;X7p(K$#)Ygg?&SEFu9^e_7{B({+9++tQvum~aYed4ukz@t3h zwT&7+BcPO~7S8Ke`1iiTGREHygy~7bXc$n6&1A_X)uz@s6j`d=WrvA!*QEPxgwSW1 z(1H37MpQ4%%__&2CJrz{XmlfSv+GOke)eFR9b%d$_-`>5Wm;4-lte=VS#DsllSlZbAanV+u@<1(`jsFPS@mHJ9UZM(E6R z>7Ezoi1%`s7eb7(j$kzaZPdzF^I6Hb6otI!7Q=VJMLo6CpMYe%QLvD)1^6C~ z%W+yr`G`ZGAJ#s$fNyT`(k{0Dws%XvUlUx#sk^u=X&Cz2@U;Xsx}R0(%NK>qsux^B zRxe@R1`SaIzHvwLMMxfULP&qH2coZ2AV&OCWbSif&`k!%T%$+(neg6;H;v={QKp@{djgOu9>Amg*)?@gT?|1!VBZ;rM-?M`~(vzKk%n&jhJXD^j zp?ia<6qYaloMG8mhbZnr5z9<{P$6w*B8yabPv|T$yy0kb+V^3{g+tQ?kYo8~C-n#NB!LK(19$pVXjnw-#qK8kyb z?@@am{0%2f-z52@hM)KvCY|^&Rxml_)EIdF-!>enSXw+&{}6jP(C!(qaNt>hyyW9Rj08=oV?n zf1lhkRh-XX%44S~bx2i@+Byhxs*0j6&s*OYN`onTM>!)9!cS(=^~+>k2#wOYVq3td zE_GmAaht~wMLeK=D);7_2^mx{x9(Q7^Mo2;Jr>90vr?W)+8u!ZEd~rvk)L?8A|XI7 zg!?S<^Li59TuMe_9}xd&FwzwnicyOdldY2j2OhN0)}0D6yjDMHobgsbgW)6x!!xF# zz3L2BMh#p@;OnavPhc(5prqql*s%c8lQwa;TAXsRwTIgR!MFUz_>50C!uEj7Lv28V zk?rt@Qp`d+^`ij`$biBy5T-N}Fwe$S5neKby~5br`q-ct>Y0G=B%oTSzka&Q{m|$@ zT$d6*(I5K@Y|F9J362-$S%RC=57NA}N{YjKama8AHJ?R2cgzO#IV7&3H z?q_d`x(tdDh%qnZO}QwGuqL43;KkHe7@M!=Mxd7`(;Zw+5uZC~%)fH1d_bdgZJRJsR%661J>TnGi%iv}Uv?+@^`3Kt z1h|f5o>B6a`vr7X!u? z9P@HjQDXO(?LEW@n{;e~%W2UX7)DbJ_%%>tAJ{R$+Y|IkSQDHwwElIx;3^h&BR0<> zHVb$vb5a-ou#KZ{q;Lpd$bo>_rtGbNu!y8&olpvrt=GX_usX1IkcY@ofk~2v0v!Z0 z6C7f4{IM@wwt~FiR~FSSHw^1Ml^05Ajk@LFc|t+L%aV07<+f3OMF#T-Z-}J!8m4_IiEv#!8kU&r{&@ykKoWQU|(6+t%z^rJ)?TnxXP)ogwBQUP+Sn z(2a%lQoxqly~n|czUk`-YyW$)PbIIep6pIQ@HGg)XGK%k00PA(_Jbhm_sP~`rv=A` zCbB9>pIzGB1cmAE-7oJ7bq%2243tWIihM5TRZky9$WFbKZjZTzwu{>FEI$a$BoEW7 zvZMG^*MNx}gLzNTLYkw=a`N01Xkn~Sh8q!e7_cO@|2o$=>)A^Xp={891%Ctf6gk+W zfh+`!#$AqRO8s-LE=&=B^@82RZg(G#Z$(Rn;0&cuxXi)_iP~qYO zH4sy{AS3Z|Cb}frx7d_}zVCQ*!gB~_LJf?&%tb0|JuN@?UrHox8%xP_qkS0*&xnwU z=D$o@q9GlaOyp5S1-MUtSet+XQAYOYZ@Gs|RrEzubS~#a+X6(|p&Z3~i zYt0U#Nzz&WKg}ACHbYOlKD%Z=7y1_Jq#{jHc8;ov_!o&9Jy&lf$-;Z|qw_f*-qGX- z)(OyQ4kl#}+!CHW)fPo$U8{JduE^rCx61#e#J=Fx0zuzo{-k1U&`fU8YCc?ZLqJPe z@4tj#a7pyzC1Cp^k1CtHz5R~p3@^w6#7kDCwi(nJM?hm1)_Yu(pD9ndi$g|p-qC_= zMW!5mcjdysM!tp3f%;BT;VRRaC?R?+LIpP_l#g;;Q0sHbFX95N~T8nJA9)kgQ@*9YZM1Vq^Jg}r(gHvj~whHn8{-WOXzw0g<; zmQfhsPra%(cuuBLUpKtLE`IX5%9CD<`)Oab`MXnmGcOTUC6k-_?J#hU%Wi0nVXO^v z&|`%LY|ToGQ^zrpT!&D|36%FC&0k`Q;YClXyH!HR2Q~ z6oR@7TR=CmIQ2o4>|S*V5V079B=EPA>sz27Vr{nL%Z%H#r zX+QDJV*j~%O+6)!GrC93;nrQhIt72u=$B3r>&~8WgCj}$qAnqG15*$Q4Ck4gyQFB3 zrrPrjJWv0zgs;)y4p2Ynog|p5 zw$VSsCXiaxmJ;U(o8abK9iI=d=Wy)dYgt0bxZ!*0jH7s)slz9*)(ri_JrznS7EYylotU*AL~RkUK>t%N zs8eo_bU{x*L66`-I@JZ(w{oPqJ$?=i$IP5Ooj3+f&=C#iOeZl^sXviP%|WKCs~}jZ zh}mQaXUF^N^TKz4(bw)THn%c2S7qstqJwUR+{_ThpN=JByx{`ufuZ}k{+o>F?)!Z) zH~)cxi*G@F|7db*u5=UhOzz=6PEhx4v$6D&7;QdIcG2Xo(?1ab=F%}op0kU|q>DKHi7;9sFc_GaG@AmD$onIX#@x{J%$aymG zO(R)eam)_JbE-P>GAox<-XF`7mZ5K?tPpY>oEH4I?qupK65uf47x1(SvfyO++tNTs zoN;8i+WGdFj|h9S4hTTKr$U!>R;3QyNT;IT0H3x`!P_kYvrp(FqQOf*XH%@srb7hZ zBcej0MSzAY2W1{IF{Rvk`}3^I6MY9$ZT^LWmWJ_$4&pzfab>?qIE9L$auSny`_w*gPE0Wc{T8!sWiYLf$~FKEuCeZO?d@$APFBk%L+$NE=j&sfP@m>p%;1)I zjT{W}ao{GTI0JZ0M}8PGgf7E4$)q#3Sf)@fh#2eORd3O92&j1( zi6rR??-$`3o{nOT~=mUVAiULVEEsb!b9<2(||yppQ9{NZxd{QP#oxYxtM`OTj0+K4qGTE6= zC|rsf$L6sfb(oxNf1m0Ey80d=y>wOj6huzu2dpUEn#6E*E4_e|} zE4s{0ei7PMsJH!JYFt(Y=`~Zwa0lT>QW+X9Xn0Aj+08}N+wkDxMpVa#bD(RKy7C7>RF$kUq=SoS;uV!~AsgEK+~%Ecn7;Kayz z2#+kA)gEhE_};K)yvc(9WW{1sGVqrmQzDgF&?(OFul^pK<700!>N&@>4EpPB&Nx`k z#YL*X zLgnNZiMP8J`H=kqWU2=Lk|Eyc%dYqW+fp%V(WLjM`XmF8{k<6AET6$Y8JF+Ms|BQokKTYD+~5Bh$_widylFD?P$yw-x++3jtqvpMlq zvTn)#oiCjQfGCv$-riypNFirHBm!ZE2-BH7+-jUr1d0p-QP?MNd9?|_-D5KGCJYUF z%5hs>wZR`Pjds-TfH)DzJF7BA#H#C{0nJ9v2B6E@0z!-CNlaSKQkN+lUyN_P)Tq?*2=Znq#P&6YVrTm~BAf6S{4Rt~BqiS= zUNm0)#C`|wvTpb8S=C!lsJB!#{WOAXo$(qUUOya`NTG0=`VFuf`{ zkKfW6QS{k$tHMIVR94{qa_H~r8YSLjrr$DPvL)Sfm?_^=D15iFIiyHGX8`sMJk5N@ z(6KB2F1cY|vY9609d?TZjLDjpbJLHXTo{JjslcX7K!gf`Owv(~sE)0OuKr=#uwJRN z8ZP?n_x!zkU?C1hKh|PL>J#m&7CFk-I|Nu4P=6C+>x^bw8)$J6Qo6~cy&aD{7dn!^ z8}IS>>UC|{ClvEqI%0q_F7#9q?BnR8ffk&fupQ<^3!~cX^`F%#A*Yx}EC({!q6vM5 z#w%O=ivLRC1N8DQlyb9t;SXX~nviPE} zq}L=FQ)%bhVli1KX1wtbmByXc zyp{xmslL$Y_`fKiW4zM!L+T*FHKIMjP|=UyicWyk98neZSkE6V5bJ}@s`brDd9(vY zXullU4);Yr2OKh$ARrgAm$wP#ORMK9P{-wmLJstWF-&Uo96X~`G&J)Sw&ZftVDm_n zfV)N+uXp?{C6gm=D+9SFatSzDL!8UvMS%Z;B;bu9YBt}kooHoNw*uEs=Ovp(6ib*y zsOGHAd+t~$DiU`OlhcXdOY)2fyXZdDeVAznoPaUwV{1^lLdsVa-GGs^4zAhtDe{i& z$Ek0$%#wHIDiVcK9Wh*)YDg)+lbSlrK^lU>8>C8hFz^foNgsFvR(IZStPOd}oHh8V z`d&Ry^(*9qXQQd_DM`Ae?bc|fGTd}BHIWTAQ|5lQFiw9(m@eKe?1&Ipon>L4!p;_C zfp0CThs)2Lc{q=X{D`@eB8&hu7rnl3SZgK33pa1RTq~n#rnj0MK>rAoIzp(`A?#OO zMX!mYkUDydWkP&4A@tNgE4dWv0&06?5^~_}b9z6kHu2BsCN8g4@b{s#N5V>)1rZv) zGQuq*hq>BKFe|FtP@@jnD{Ga#dONB0NafFmm#MbEB#^lb&{%tIlk@F5>W|Jd4ck25 z3ob7TAlXw7RSHW1W)QL#h=wKH;>0V)|I~n=IL!nI^VT@7{dX`X7YvAz(c~lSQDxD= zWrzySCYI8$#Y$9R&-Z#8GKA77dI&PdnC%12O~IS7(7#O;eg(k9%b7!6{VKj>6c>ar!N1`YcEU zr#4(C9pF(x&4M-*ZjVenfz&Uc4{8s3 zQo!Y~C8B|ePSl={9DAEiRr?o1Ji5^7``0TSEg%yl?!~%?e&F5}VbM!`<75&K#Y{KT zw=(rVC>350i;W}~QWsNb3gCMtGC(>3s{l|L7Lne#D7hN97qVS`7C7K4eUcZ+;W(ue z_OFyVz5Iz65L0^&fB4tbnORV$-VCME0hDGn5V{$_W8e%nZZlOy;(389XlWu7t;LLC zjdI@65VX%7|LFs4ajWU$7D)R*!{N;Itt9gXQ6oGvE*AU7$%xYA&aOZR55Mvu?A0N2 z5M!S9svv#;bC3uHv>E{~0n6Ea-eYdbtTN7Pd47Hw`xjl@W! zplb3jizC|i>m`PpYZB7r>3}~dv-oS{V&jQ|1d#@GJ!H%pAglxq2grc?o+CFWnl?ns zNwE!3a_|GKd!&;)=k)K?v}ub=%hb8^V1t9He%?x&mdR;Nu}r6xT5C-zQjBPgC`UN} zP{yPGI#EOb2|%`K5PExjJxWyR5SZV+g$^`IT5Qus&8pE69 zzRxx}2!<}~~fZ#p06QC91+Y0d2v0;dg~vI1Xh5z z2r#n24(Y(3#%IihuSAU1h%?RLlmqC5=7jXfsu19z!plD+p$}2kJ!P213ygEq)R6aQ zXaNm~X_1UkA6gy-I}!ekgvyA+9LtKQ7)BL3LXINK(voP!Y*B z@COP&OG#G?0x$d}9AHgHU?q55sfdLZuE?n@R|lIG;0!_!S*`(;6k^MaKNzR@aaA~b z1L>AD-9#vh%<*Lr_ospd;K+5~TGJ==6?vPztMMV!_`fabU+5ay4vQy)f0Hk?*Cj88 z1C^UHpa6FleQLaD#I@p2sE2*WPz@p)z&Crrd<4Ja{G3M*?Igg!o1=vKosObmvp?TG zlY$o!pPb(xk;lG|hrwRyjJ=6)8K4mf)bb}p0##SBLbaJQF)t*w-Sv!8>VOvh6OYP$ z_(^r&HW*n=%FlAFP_*y`SI>9^M2aBPxPsJls+da`!{ez43f{lVCIBVW9E1QM$mPxA9TTzjjZ^Io zUOk*V35{FOHsg(z69pMmySm(naQZiB+C(RJZU(B;j*DtnO0npL8X`AqT?b;F$%dmv z59}`>bJt1V+jgG6yiLa(&_7q?af=a3Y^YfG65HWJOQc!!9V+KD@tx~eK>>tV8k$h;Y`7*eZ$iBc zQmCW@c`vuJVRF`tgF($_2`cma?kgR(Ta2YN^(Ur&IPqJiVTDOxbOx3;GaFj-mq-1w z@lvQ%^_X40QNCU4&0k`CYKm>f4!80K=@veYQPVa(RFk>*<21CafW`z8OlVv|A0P&M z7E6LtW(8gtP-8gjfd*j30mxIYvsIr8>#Co~!_nJfYA_q6O`RV>VnL)BN~Kx$t-rhR zV1bMc=@Ssa_A%(%)feM6PQ>Z?3J4tilr=5m{tCml!kx*zS~lYZYjvh8_!W?9v%E z)dLBqA;tqdoj2Q4kRUU2!eOG4q21wPB(y_=5iW-1!>uO{*R1Rw&bUWmy#Is=yaqGYwzYAb~JXtRZnd#~LjQLSIFnxP^s# zkB>E;$kpXOAp7}Z7zn5C9oAL}B~V-aKbd?h|7`W0DD*W(WRwQNyp3n^MF<&!A|Osi z30)Cc{n*b7LVd0^Az@^s3w`n%i(kp&TBV?ZYQYl}mf6mB+=$D`#Z$&(UMj09WF_*d z^b73Ykopi`hf@{U?y6-;G4!D0%ryHZ^2i$Ke;>!fUWo`W&+SvifBXJN@Nxm`@j`9> z@EdsDbU(Lisu7Re3iM6tA*%;%qaS{S8DnPn^F({C{EK2TQz@XS^9^BFpY-vDjv^3# zCRT``xjr({)7|K^d$GOjG4%D=fGkOs;|q;t<9EmmjQOd;m3=}J534s|B_u$Z<2OM0 zL|Pnl2R+?(6*swQu9W;L(FmBG!hp)SvR!NsJrF?c+#~qQ$j1o3uNd4l;k)KdMA~VH z;6x-)n}QXfWx6O9W6g7ICg>4Nz}N!;!->{Y0J?&*XkBjddaupKs^CVEVS_sj^m|MY z^ME^WHJJ|>dGcc^r-}sfCtf@0(FP;}S^I;)=*eBAEK^Yv=p=<3(+cablNk1m95)&m zEHE|0E)FaLpH%n*Ltg1{ARv$j)mg7iCw%|Ie;sK$fnneJO-{PvEkj@<=j1J+?MO7B z;Zl8=05^((sH)gzsI0=X*PzjL#S8fQ>_+s0UUpBMsE8 z2n(uV^)@gf@Q#bO2iuD#c==?ia53h0zCMC&^AE%YwD zC@>U+gn^7mgxron2n!+HF$;JJFbD(`R1n*Kn%3D$#xdLrq*8DYq6H#%M)I*7h^}PT zxM)dI-HUP39gPoj-Wia2Gd_jv#w$l>S#x=A`j7Oj-&0646WK(KaA|2+7rM+;F}2-9 zFGWe%9h$Mopr={x+=LO^U1mOXsTD@^BG znvAIjxF+cB1IPc$OZ;>vN-2-yR~(+(J+&L*P8s!1Z}|Rd90g&SPWt&?mrv(eB=-e& zX2WiQ2ozq18ji$MugVwb2X_K{K_qwy050-`x^%*IBmVFwRvfE6PN9?hI>0hrMy5QZ zT>!ETXoJEg8G}Vl?J0uaZ`=(HIiMGGx?k3>ua9r*5eTuvsvQ<%1|tWtD0)f^Vsi3d z;v^FwXHx#uZ{=Em^iAW;c5tFBUDLxpeUR*wz$)9w0W^ei)Nz`S;%?Nq*_N4_sP@$7 zB+a(w*PU1CvRL3uz+fF#HKt(43s@l9T)^J7y6pexushTqs@%Q0wsSWGQPkSAOtx;M zINd=jbo#DX@3_*A)_Xo;41j#6`=Ji{>Q+~OyfaV-U2dFtnU;2z{Vg;rBKMe9k zpE-lfEdaI|aOI6UeYcD06s`(IgJ6_w?_g=PK%z|b1DQ)iVb1zXho>Fo6zsv4$6QLo zr=9Shp+nzu`WkL(?~*qIj7I?qT;e1$aLJIOQYCyU(mnhUg4X%Y9})_XoO01P{KhsU z5|OZG1YUA&s8AD;Svyb%L26IbfD(#mgVt*(EK2duk6t>VfSf0Q2;1^k#3zJGy{Q4k zFZBK=I?Apl`+hWeFA@Mh?=RbI4-~S&?Nl&lp&RBd1UhPO)dZGEs#?c2^CR=aG{TKI z$+KK}0ia9uiLJ-e@S#Bk)SS$sSEB3=Cw)eRN;fHAab9evc?K;ANNwQ5mrozAkH62! zsOmF+@3?1##?qQ4)A($=vvn@hKQp2I* zLMCvWA!eGgM2V2(A-h2-a!kpPBry#{;%OI<`!X5d>0v-RJ>mP&g%fM{r9Lns$yPC9 z&b^Rx4^!C&V@thfs>z;-Y-bwgTuH4-M5H|L<C$JeRFP0)#sj9< zF5?fse{GW_$ofL+*d#37fSe=^MMLlal*7?A6p}sS2cVcxtWo~~>*Bkq5h;s({}bQX zc`yl$9R^Rmt^Le>&Pm#mLu>f79WDn;kYU}=BSRY=i%85L*ZQ=7r`T~9Mx?He z&GMXTPZxoSXc7rloD5hO@+2qYY}cHXs@Pnb4GJ!EMtN-_$qrq+RL>t!s6bjkN6qMV z@^`g=2ha&0VsngMAg0XSknWQFG4|+3i_*ZKr|eHd2<6|wT%CBI{N}OQ-&=_R)j8jcx(Oyb#YrB6PUJtHL2~$vG$2i1`jV@3OVZ0B zeeEK^G1q&`s8P$^+4idWzn@L*zta_~+PxkU*G;tS357{3v}Mk29kgZm`|nnMQ$65L zkBu54ioPByP`V@Fgb))^FEN(RCFvjiib@&Gq=iQ0Q~ZcG~p|Dood+oUkvKz z`?UeN2Uh`HnsyEtX<`{ZU&b-vuKk3?cJ_?WK!x4R*Xz(*`GUHi;W zHvpORQ{VT&m^w#&Fn$J3!B!q#hZ*V4BW4PEZ&v6A@JtGxl)$f5M)E84eQ*pbk=fn} zab&H)VUHJbHQ#nwP-%u1e>qHSXweEhxXEeoqw|E1Ln%M?5G~tU-80Z>XMX}Fs#M-Bi5$gBx6nB)konKy`~X{5SFDLqL$soRPQVr-Oe|Qcpu7a=qOhWx?%!I3EBqMt z;u<*@z=@n+&N5_T;ciP1fki?vC%{NdHoy=>B=`!gh;)qW40(aOYQG8qZik3*BzC7Yp$#bGf(2~q!~BIFFlsQhdIu0V@^VOM*jsY^YR z7u(Uwze5)~GYTw4wAF6#T}c z_g8-lYx^&BMY_qxvTWaTF!)E`t!|>thwT+B(;p65Vh%7_f}I{H3W;q~h{mR6&1)?h z-X9XpgK=}JoOx!@(Q#MVvfI+z?D%btB^EuFfuK1;xZ?zz-vUGu{{^q^OmVd}w^1%yNn5KSdsdH9 zb2!}q#9|!T6;RcT9jI#Xz5+gjbiHnMBD-_K0hxFwrMm+Y?~)@;ZeGPi!+!vJ{IAyd zN^&3oCNg#IIukNQ5+>q2nV}5Kc!9s>Nb<}ti9PfNo+6n12`lq!#O#V340zYOf1BTH_`m;3Az0+<+gGb+&9D35O zxja>5$sZU0e?_BaN+elCTsCK`ewWZyzOSUf_xbWooFT7)d2v)#K5!{lwqwzT`pIpb zAg@6o($RtBM7VK*d5kHk#L)jh6qJgEy)f(xD9G3~qqw%-?vykhjdpBcVj7QL36|&s zGKZ)4et?rAT2{=~dq*C}wqpnJv``q|NNUg1Z5Ruj$>(hk8j=RqlUI8wrbrAL`3s|8r|r&)JHb((w>HfWQ4M#16sBY{jPet#qY~8nwn@ue@JM5!~ZfC^-?+IEnpv>epZZ-RMc0K#oVKkWagNI(k0-HcG z*zww${4GPia1EnsDoXH0#%ZR`rsCXX>di_ zbr0d23AS${NDq?bQ|2SgcY)f(RdHOwN_!F^PJ42Zr*cWmfk(A}SZ_d`5YV)>oY{cC zpdtgOvo-KsjRtV}SDar@3^Hf{sw4kwmyO~Ob((@Z3#mcBz^fBZMS3X$j%Dm1|07b8 z22X?$d1y~ZxPG`$h-?(*1kJ(#Vy^axk5XqsOhi^AH(>$|Aa$R2LW|8LVes2Ed?Vg^ z^qGjj!9W34hv0TlwCrk=cZej9-74o*{o2{i`0O7Gb*AJ>I$&g>B(QDmDY*KQUnSG; zkZb)D4)Z8^a_6H6tingGK9e(37AoX}CK}=VADHVR>el^_E%rhN86npIVyQx?ipmVg z>R{Le-JaB$aJIL=SknmCX{*<$2jj3NESn=fJLAJzLHdCQ>XbL9Adx=2^l9%iXyBLx zU@XpbTG$sDKLxx&2$yHd!L)M@p<>$s_&wy`8MAJL2~3m3bFxPQde|rwrW~zK-&W^u zr?RoXU|b|GY6{li;|BlE>L30f3qf+t3{6Q>ZwM*^4v_zw>U zK{Fl^3yOD>eThJr#NP!O98elCpFscuFBJIvp7ziQpQLkM11t%ThGY6eK?MWJpqIs= zklzF`BZ4JhG3=X%&=-^kH@@cx$U{*agiRQ1u^rh2EH5a`Ak{NDnU+@4>lv!2rHbfI zp!#u6yro9@N7c=;*IS-3%DRwlv+Gca+p|${%wRfL8_AD9I25$&50H zIClxBpyocx*j070(RUdX_{FD)7L18A1ExS;fY0^c!H~*U{$uUxb#7Lv|C3}SKJW(#ZpU-(`X3eON*L!& zBZKHfY{;R*hG*O&1Ij8RofpL-a`Ja%@bgAzp}DUXXJ>d6-`dx3P-8-r)Ym)PaUvcQ|`y!5g@&aTgmkk|AqBG8=e2 z(uRBwZCt2=%leaQRt(u+g)L9A)0n>_+=dC7d9x7EY~+Gx)ebcHM~-7PaT|XC47Rrk zj38;+D?7)^}GEkU2nSN5-6ub^HTe}o~eZn&PbL0 zZ2SqFWS-lh86uFd{A~lIH`Gj}3-y+68bGZ1gGH(_8h85IT6_wRPyZIuKY+a1>Aqoa z-!*3@(7C==bQotITD)pb#NElJuNEZf>0X5mjBoOe;G8s_1hfA@=PezMyMC2h*wg@0K$34FWjc$}*{otQc8Ov0Nq>k-bfjA=UgNUNYQzu;=S|Um? z&2prvLk?Svy&EY4y@ef$CggaXc{yEai|grGsC#_6I$-b(HfSb?gtrWWkdo8LhvvKa`i#n;Z+8BGnEQ^skR34R{*)9%A!M8WG(nv1{HqHWtr(jG z-IO?ue-G~#NWmv0U}oK=w&$jAEq3>C#aMnS7sU&=k&P=|CUCH*_mSzO$AC5ry_HevhFj59mJmp2dtNUDpT6rrjE zvnRGMQiP2!5Ggn|l>d;Z(y8DS$i6mbYDu4r#wnJ|rP0?)xTwA9Zl*PNAFX@xad>o5 z`kA*NyIDGyNcHEXHx`M<%y%pw3JPpKdi08UIQ>&ecd-bvKwOayp%#?`A52C+{!d>r zJoeFQxh+;(AwJydS-D zHIX{Ufb4oVr-CqZmv)fZRD5g4pYsd~cVuT$vn(HmcOO%b^`vZLkKRl$^-4^bOekB3 zZau4BmzkmzXcomL&QTE2Mpu>^JEk4iOVL(f6SEMxcUvrbtV}-EKk@-*i2Hv$T^D6b zZ)F#5p!vc;a4{w|>4(~zVgutuDP2D|MZ@!nVJ|L8E9BX8Kk@I8RPLT&%pnVh{Y@0s z-?S8nFw1ts7I-8*ck8juA?tK<$ZYg;m*Kb9+kN#;y8ET8*^Yf8)0hyR>fN6BhL(_nB1c zQzf#31WmaE@x>M|;(g5b7fSB|z<7DS;LNU7VH|TIYzW87YW(GRO}f>`9@!WP$^XTs zh0-6vUxev3(=5DpC;t2@=qbDP)X7fYlUS2Co$_;8l%QmjEUQlXO`-$q}4 z-%x2?dPTZr6m#U5Rj-svh-N?~}NZ9@5MV!qA45-aEFwYOaAXQ-_dlTNE9C5@!9kzy>@2HSjS2U)9qJQ+Dx z&(VX(QweY4Z5gUYwimveTq|F%(5S);1Pn@Z4_oD3?I_ z36aYiCIn}FN(C9Ev9g{$d+iq(t+|pi)!PCcGiwHF$Y1(z3|-m{St+e_3*}b5BT=sw z%W>%+7P`}4vj_k$LoBOv?lPY(O&k;a;fJa+Sro<WD-LKA39>V% zqrkFmuuSXMtik6lVrZ<>Pqy?&?wDGVcuSBu_8n~SXNQY?9Ner9tr@LS4~o1UerjoL zlw;hMTPxj88p?Xk7tz1~yKJ`bs1^R!_`KpdM+@~UszZc37StzcJCBatR z^#Zp*rgT)3G%y!3rhw5q;@d>Z|rax0w8n6cYnO%H`(M#X;O z3a4`P@&~J6SSw@rU+M@Mj}x!~>6E{+B*;+ZvNLjmnW{nAhf#((<4TxMEk!<-$u-aRH@TbRsfOb(@p8j)bV_ABHh5Z7zP*DUBRQHWoc4cGGJf2nR4 z(_XeMwotGh>T5j4CdoGXheM8gl9W*CI|U)nPZ%Q39vMU0!MfsufRkxa3fo^Qcq3Th z1?DJnD2O*SQ~fE1P>3f9Q+I!Ed|{|`s?z7ndd3NLIwIt5fnC&ySdyu&zW4&QL=h(A zy*0;hmyayhBQ|@2C5H$$g%bYod8Y5ug^YzSA)MvlgiV$r94n2%PEoIU9=T$2jx~7V zLr!;2_QOZ|d_;zIUKoNAGpU+`Wzve5PJJ?Nfd5gdpVn`G?xOG%2M5ynHC!$u*s`|b zuYM}=Qubx7A)?&4B9wc3*O{%LfT{If2vSQFs-K$GJG?TAS?M*`xmBM!CJT!oF^oQW zN3b=w*XdmOuO z-e4~5rp3OP#Qr=_^sKP@1_Tb`vI=eOR@+iRU8l`Np#XiCAqrL6%*%mT#j7)8S#iL*Iq#syF#|O7P$x%EWTm-fJ*-hJI><7xsP;;xwBmGMg9$!K zDxHy=V~lLK@!HK@S$|zbwtiz@pwhwJn=Ce(p47Wn5=dY{K8NSlJgV@7AQO5v)rI*_V>u*BY*RSh5lnKHn3|z2b~{9l>;Z zXfPDN>M}{embK`sWJ|XPIK4)BJ{gQ+K4c-u9e&~Wmd-B!{Je!N&kOU%+7B1Id1B1N zHia^9tI`p1AMA_?ybj|dhgEg8(QQJwJng$-W&dLjh&Ti4$vcVbxxdmF(@$QKlbul$ zBy+5gdkTjRqVDyKOh#c5{wpglQ2bP3IzAkUd>sTX6CZdo?Zre-6ux%oL#pufYp+hsp3kQIb$h`4N|wIq)7m}D zsV9&=lL+aqo^z4n^!l*s9I#@7n|~cuJLuB?+ch)I`Cj@Rf2?d*M5D4=xxjVWE6yXe zpSJU4k}h^dN?fwyJ?$7P&yBrF=tZ5(cv}2A8-?R1`w+xC@&44!XRaqG;zG}e9I+c; zjGj_C%AP_J?lZM}+%HZn-}^QH(OqA0+(l|gjDg8IHw_;}=PEus&P+m_UK=7I-QXn+n>K7 z+#U-oVgd&X$fTN)w5AYG(m)bvu1MikA^g?sMV;u|>I<>W5UjSCR$gN?qRrUaGhIEk zgsIpWQERF5?VC+36)c@5*ndBDwNZ(um|T>4Oj{{wi$305ZeGr~y$cSd|Hj1)(_B?n*1LtV;Db|KAp$i~chStk$!{ z0yL#~i%Z&)cID#T;p(lIDxReKcB`@|jb+&+*=T-r$jUn>TYXQu0WjVFKbQK%_s9Vh z{pm6pJz|SP!!P7sJ~{`#L(G0D(>kLeYs^9K3x0xfrzw_wcU!E3r)orZF(HgwJYe7; zUN@nUczrLzYJ;FECWrLr>^YD7`!7~3Fw=bX?Pfj1J^JpDsUG^wGs1*;`Hyjr@uxT6 zrXBs!l-xty;Bl9kcUvjZ@`sghbjKN&LFe$}RlJw|@Zl#?g0wtNru7E0-0~l3n_oU= zIzkqjP%Z}!UMfi7O#9XJ4H1(1S04rEVCP@Yr3}nb4YOVMnbHpG<^y~>^v6*OHNUg@Ok=ga1M)}P&wA^;7=B#AzuJdliJgwZ~V~} zzl7?wA=&81Ojwi-JsCjZD}y4XW>nC7byYZxgq<6^?~o z%H{(t{FX<|N)3ph(uHEPsbMRAMW}hwT9`*V8m_~#4Hc|=l#~!Jlj1%q(U)k_66fWt zOV&vdn8K|}Qx7HlwZDS3$db!j;VZ5mlGBazodQ;hy7;GlKy{4^OZYvd^5SI*#Qp~( zL2{22J-u4*Xz>hNlx~D9VfHQL_m(#@*Ssozk5YCLPY)Jp`+kQY-s*U-^+sHgaFSdZ zTuj~Py}fK3^`Y&Bq#~Aky%K$u8)9O{i#Q$Oc(_!IFwZkl zo9#Vxe2wDfnbx#rUw=X_)6V##!zovicY5BX6%u`R}$GPt$X9dyNJpVkAj zwjbkd$>MFQ?YN)jwGqJf($}3F=nB(06Bef zzE`=mJ>3$v9!VgNopy^(*~?j5^}p#@qR*08nKBgh%;kB`S=Eh$FrOCfZ5>z8_Yzf2 zqe|5`YoK@gZQFOhscX2dUfZ+W2dUc(RY|=$#534^ zMtMREBY-g(B;@YzkOinu7jOat}~FRSObc#nwRl?apBr-U;5 zrT=&LPC>6fWF@8*eM)?o0>OVWEg*zHWws7jXGa!*H2cKt8`Z50C$#=ZGrq>Hn|srH z(%=qlzaKv-B!yf`0$Sc*j-s2*ZjCeekgck?eDPCpIsVPIOLxknE}qngoV2>=ts;C> z!q~;ZzO9jm%Cjz*&XpzUH!eae8NTlRtD4W#o)k5U@hDbN87674MW$VcG(dLflCmtT z`+^pd514lf66`9-AlQ`*eOkr%i;e=wCFluC1ZvE5(O-D3i$Vpoep`o;EVsXHEq`58 zUm}HcP8H^Q8&xq!sl8h7f&AB#EEl$NI!EQj1M~3hEW;a%HuFDR5>E`?QfwuX4j>wj zn>)QoJ5!pdoVD2hKnP(zp>gf0@>EuT*RZ-mTuniR6o~?yvK}>e-F^}c+cc>jf27w z)e85>gQ|dyOADiJO^W-F!8y35^#r6FZCRj5=@pxBhg@S(lQIat@|^g%XBlbU_I-1UO15ERSrCl(vD z2!C@kKr3L(Nvh`TCy!U{->dpe^Hb!4QMh;-v8)7{7@jFk2<)oav)Pt<9jAE)s_rdBO zy6W{ZoKA`3Ct=(Ix`X}_oE6dcJn;lbyx0`tyb%X*5+3@XQIMT+mXfNBoo`2{X{(GG zOZ&1o0@i_kU|GA9%&KoD+dJOt(MUXhkKcB%M+tVGGvI4~;AoczY`m+|ehE)LPL&G# zzIgaGZS$#4!B(GpzwmqC*rCo-4~6%9OxCwZiS>-;JX!#{6Q7LxkN}1VYuS)1{9`wL z`(i$K+dtu48Rp@p{_}M4FR~#c zW^=y1wrak;BG+k<3;v47ERWXIY9Hm?(N(_WsKc5loLRJ%t1AczvVc;bTl@p_sMX$l zb;Z&CD-{Jr3kW&|kV-cv9$68H{Mfzr`$@(r%_6z()-QK(G27HXx0_}@>=K5N+M4S# z=BQUg6?nwUYQm*SRI`s%!iwZU{wuvwLdLe8Nk}qIQ2ktxIe75Ilu`k5$Yu&HY?KPb z0lwN=>HT{tSG($*b=wx_#`Rqo7mCw#UGD(5)pr23dRI{Ov&m~xn+y$X>cV;Z#hAV6 zat0kz(^5IS#njcP-K%16d8F#~D?Bv^LvguxBjjOD+dTOV7QNldvNw!P zJ|)^J`-UD>Um|R_pRMfA)k(J+!$-GN$&kY(Hg{%c52;*GqU?!HGKTY7Y<)Bsq*8(l zUTNxOdh@E9H>x`_tGGcE3Xz#Xb-7Z!2?#H)mZx;we{Z>il!E`NF@ZrE8UpphK zhjQc#E~C5gS$tenqwEoJf+hC8OLEaV>rVtK*g+C6D~Q=V=gvW|cte7~1>imHSTn6h zF-&w?Z}q3X*ej)-<;``ILFYUFn3}t_u87&E;G(B_dWUDzgU7o^eb@`BFF6h#i zFmBOzX>a%V*}n7^f=nER908I66f`I z1HgXr&Xr@$9qE=AlwGRb`^K`xV@?h5V|gznWuaumjWFeIiIERHbY|3D+*G_O(AzTs ze;m1ZOM6N_k!8rUg(c^jp)vh{!Jf?cUpRMN=f}zx4%0=$-46}q6{p_14jZc@q5E~R z`uTFUoA@=ZSNnvMHpY89Uk~E#95$q@`2ZP_EoEgc-TX)%#F=jZJrx~S-;(qB3&u-+ ziBet<9H29+kA7D+CLsS0yUp%5b60=$67ONgo!!PbD+8#-O?G7H#B5eWHw8~=!+G)> z5q%zg7V3{VbSs+smb=7fB%#QBg_@@(Um=4KKYM&snuUq1V4F76aCo(I8Je}prmji5 zW8Us_P=Pw1awMLm9^%^ARVV}gG{Fx86{!qF)f<@B*yksqLa{i;6=1feMub(rg zNl4TT4@JadSZJ<-0g;L}JGbTL-~cFPy>*Uk>gw{fwI=_gF^hUA?u{V{$wqys#oXR6 zwctQeZLr;Xx|ly|oG#iTP|)A%l(&>>l|jtn__EsoOwl- z1UFz&+d6WEskhI0+ai4*@=hD5DMGRjG4VshJaAxampQWb^gETLEXqL6Kgm|#C=A)| z*9KFa?X-A0D|Ap>$G=s|>3P6_ly66oM3&)GlIBxiZmdpjS`J(sMeXJ%%sjEEWHo`*taPp%m$$9b zR9ZPtp@=DkH#dXGm|%f5>We>qOxRE1uKk6d#=|oKEm_)cT+3PU&4hZW3yIk~j@#z? z4%<}KjkSx3%6{qkj4g%GJ)r3BLZ5tMMY~Dapr2o9E2J%Ls5_VU?OHg;GwHYUqIzpD zJ$Z%|N`;7|nSEV4@~R&5`U*?djk*06@-Unotk7rk>X!QO=r%$IAgVE7mu^`zF2gQ+ z=n5rN}1y$q8*}|VX{w}p{_29cGIAm=)u2A3y_J&y0g6UfmXxz8T0<7!W zA`b(V>wtQR_$4C1udhQsLZb>*i_}4hY-xWN+BAQ~aFyVcDx1yBQUIAb?;1+K%R@zH z)?&K$=c;wrVq$w!g!iHB)@$icd7Z^jD)`S=*yUXt`n7Lue{u6q+nUHdq|Uxg2HEME z9rT|&P{)6k6bl(=+udHXdumbtND*-d3UQ;>;sv7V|La?*y4+11>SvITj+vSd7-E>q zNf;k`km(FC9#V*R9~R=}_vv+bqo#MK$`aHEVrnkGKK;XzWIMBH_`&D=oD-UJ_biGs zE)7fR>1J!D`^@fVIFT8DQDMtxjb9dwyVp6?s2V)dV$AJ<#($4C&p{P zO)JPu^@n_)MtkHuKGhlcgO8EwsNlm9VhDyOXAK^PKd|5oRVaY#4m$UPq0XELaE=M& z2tCV8i-IF*dew6^_pHbY`Uy0XO(a+`y_LTmT*N#x$9#y9UZbL84AUzl>fabwGjb3t zhWa-xT54=dY>RELscTs8{Fm-CYlC4Chb4APY4<~|rpt^hrKHKWYUO`=MU~B~4K6B5 zC%tnT!b(*Y{5}Zow=@NfB#f#!`y^M+k7Vz+5nS4!vXGW0php}kXK?#LI~{KUD+I}l z^i(9($HEzc=l=0NNS>kepD!e@(Yo{Q7c`_fNGJ2v+a9-<3?J{v4e8&om{oBsV7eHM zzz6)0E<&YA+`O!3L*BV}x$1p>0ZLix0;lresQp_d^RUG>upf_zQ?Ql!JXb3UX&N}^ z)n%$Z6ihZ=;jnLq>wWW!;z3u1sjrv)P=21!+hdJX6&}W;vWB&`Xyw^E4V`CZpeBFg z@Vk4$LJ_0rZMwf}m>JjGU_wFG{Bj^(M$qpoc~C+RW`d=qo;*n5g#m!6Zh>Y8w+z7H zY!u5MWz;=~ycs0Rc%)iO0ZT7-U`>3om@jj1^GWe_7?zrU@Px~)$hWOj>z&NjRehKE zr1`k5tV*}AKX=m#_Ksls`$7gRy-%rlP-`WWH@B0lE7v?96Xp3jLJype^@2(&Lt|TX zE=EwizFGZghVLrLGF#R2J;ExZEl42fuM;|65aysD`}7ttXJ_7xraww#C%GWcXhqtj zZ3h>SA_iDJO(zGfe7=tlpx?Pqu~r9~bx!cteAwov{xQh4lA~Md*j2WcJ(BDBF+8ll z2iD*}_Mmihe3N>er-VB!Jv!_6+?fi+2<861hJXQ{-LF`s_Khm9RoMg^>dHcNwa@A@ zjd-#2$e3+wk?)e~^Lb`f2&e9X_V&Uo^IeyH zgntd_mcwh%io^ynosN(}Ai96P1T@ZSp@LtVUWW?a6Yh3h!TYWDn-R{%fP&UhT`$bCBe8G|&SLu_BH8XT7y_K3$%Tu-X zYoc8Gc3fNMK|(O!+;^**b$6)nWvU@UIAEhtA6N)-KIIkk2B+lmc5-n(ZjeXOJ5XK& zrews_%}4mo532v{TwHJxp3C22i$1EAkt67O}#CUnT` zikGe6rH}n2U%BZov0~pU`&2xON@lrmCu(jb_w3a7%KM>IS;{k|^yyq1)zGp)?%65A z7CaMFDbtRr_Q;7U>SCMo(?M9T(!yle@-uLW^66LXo?dP4Yvrf{>MrXp=4 zZ-kCOl9azdB#ni>-|D$Uf}r;yTDFyI4Y~%qWp?0DX=By~>yu460 zUM0KZAy0Z`z#~5AQI&_iHSoZQ=Ubo4`r?1m&_>fllC4rMkJ+l+&*0-*B+CWtWT6Tg z;gdnN-(l;~9xHVr zt!3>`haZOUxz?#Syxs48od86y%DWq5p@EVk+>W;o_+C6Q9RRE)!iY~#D{eL5(RPhO zv&i~5nN%f-uW{+H2lpk6$dyKk9$k0{=9PLuM1ZfDVyAs? z^nqhIQ&-hI(@|SyYyIN0OeO11+}rd+x-xj!y^j#^GE{Vp_Fb&&5IXtzeDK}PN=M_( zCmyz5v4srsi=|nCUxdY+B|EpaKHbf?SM-T&e;v{kZ&lgwPZGds|U-s-b|GU$QSwYxcIb zAF*}ByuQtRdg#5**7YWr8E&W*mpQ~^#HJp_GM%?vcyrrk*hzKIRQmR~S;%?Uvm<7_ zlHLRKc2T7BLXRE{n}@s?G#tNaed$6czXlu(b|qo;@Emb z$C^B5C5ft+SETyr?~_w-KX{R%dz_0;X+-(qjuBK8gEn{ zG6j6AXftvpV=0PiUPU10QPFfx=HgIPa&)q-YlyeP)?odEir}OXJ(2RQF24{Pig9WF za9Bq_uYyk{b&kZ#R9Ia{>k0l1QQXw)BJoU?Va;FB|FiW(`)<4lLlu0{|9hlGVIo~D zXVWx7L&c{>zSS=k%PgQR(aj0?nLC&4@^WY2+&3MD3XVZ*^^BSV9_FV5x-#u0dYll_ z&HV=w49fsazr3J&f$G`$Bvo(woi~kond*tTmtH&6*iPqFWof?^v@ML5(u)hz;3z1q zqPCB2&vRi|wsvE2oQq^^f6l_$LAGrDnSX;ONxWjw$YLR?;Fs5*647oX;gF>P`G+b( z2RvJeoSJFz+0E!5Ap^}_NcEEziN2h8?DQHLl1d=QkF5g<(SD8tM!eZRpS{vhCDEVn zc(hM6TG~HKjZq(7d~e>@ARs%}TJ(N!K}IJpBD8!eo zV{L!0ve+JPi$isVEydC{`Lvt?>AMt8IYy^x5kBee5ZV3?L5%z9(IcJ7dk&$+ZWUJo2^zAR9x&XrKaMri)Bra!AIy!CW zo7K%C@cO&f6s2=z8n0sJAX`_=q5aQlb))N~pAiz)*selpa(> zy1Q{eU_cB)8k7!^52HdXbw!luZ9D5pO z8}7%O67$^C$ouk$mE-7VaF=A*B?)WJkC7p_SiN?)lJAHFaSsf~N*?0G2F-rWB66o0 zHWK3VJj@p2y0h}RuZo!8!FKdswB-&PHg?~R0t<1KXAwX6e(_hPb9XdVSqBP+wi_52 zK?t?(m)oVebZux!$6#*c0A3n42XoSg6<_PtG7|FNS1WFL4EPlFzq!X$%Hz8ZGdN+% z9KVA)0>?Dx0ZOm`eG@4+`r4Azh@8ZTT+CadO5dBj@j&5?bT%0jBUT=as4}|>L-(%> zaIKpBcGvK2Nc`-(1G>3yu>nweV5w1RS0wxs?;pZin|ccO^Y+>zAGZ-P6Mvk&C-eQr zGb`$54#Qn^uNsSU97C_GpeH4O^;tqT7f+W4p6J?-Mg zL^L@A#)m*39d5O`O743djG`BpJajx$3Oe-ne;3dt0<~R4(zz@i`hccZqo>So;H2sj zWJcBX41S1zCg!UBUI;lAj%k&MbKu4}UX)o{s9qQI3<^JDjD-H?;UXeq+f|SM9e0O$Y8MKCrbYg#NGZ)LC1pR!7eR@3TKEk==)ZiGbCC+- zw9QP5t~@v-vX1OhW_Ca@9$}A6bUYA|hv+*^P`SSJ)lQ1-$g*nw{_a zI3`6T`c^OWwSe(>8Z;iC0d1fiSZ@~xuLbGS|IcTOtm$#o+_Sqv0Gh-^%^;oZ z!6O!ju&mojN!&05@}YoFv9Jnk(62{%(s*fSccZOi!tjdOEYj5C(?&?`pgYds*zyAE z(9vLljq{ZGZCyG8I9rh^)u;zQ+CaA+rIb>-4yms7s~_Aju=}w zgR_!^dbJ_B@|0zn`hl$;{5;@90J7<&Ho%nFfepT^F>HT-@XWo6Qx?$0>O9_or*hm0 zz3#zo0Tl7ialn1g!rUDM{q?w3WFKb`_4e{b><2wG?pLIw=9RxWc#)6xy;ov-VLF}; zsLIRH&)GG7YvQB8t34!$h~MaNZ^OF+DGxWq+Hd*+5KdJ-6n%Tls*sV^XW>aKvn3#x z%lle_9rPzwck&-#y?tN2QU=J!s&7Gs8!!t<5uq(768G4xCeBwJ{(>gEQK-1nelrBBe z{p-MEi4~RaJv{b&t+ZlRWlIhWz)&|S)nercT7kN2Zs@{dI4-2VrE#veYQ!|jz$&{hD9RkB>Ltq1}Y>u8Fj2yj=xDM6StT(c|r zp0GMge+R496YdVK<rzO5Y%RQ1BG!faPkqI0p{*w2%EJ4S9m(2<|1@GTo#Z_lxw=W>BXpj#=4W@fJ5q>?MVd=SuI0UjT2Pv$d@{js17)Ae5#=CkS=cz;2?Rss}tdjc7kW>pHLf2TFtDZcG0= z(1BG2x)2nwf-As%S|uYA-FmGsyL+>(y-@joe35#}_eLwe^v6pHS34nbqk-Q%-d|Vg zTRgWCUy*Ch1iJo3qrQ9PH6zg1!_tutqmoB#Hy`KCkXds zmaF*e+LQB~@!O^H_7Xq)=J9A=S{k7Fb3L{SPp*yB{A$tZR`rM$Hg$R=l)F29D9C3>`havQCGERW9?~REHm~isH1RHGmkIq z0{Q3`Rp|)azgoF0et?QB%;Isz?}OsPro=CLvq z49cf$wu63CEbP!NCPWi>M2>kfjD`owMlbT}NM+lmv7!S9hbYW#ASnmw-l(`}r&=j- zx}c7Mk2vOAXR2Sb;OxJ&HH2Id?#YxEkm{^`ygH+<)3*CbPoCGnO`W@0;6IUWKU=%L zX9;)iDmE(!%(STYAOSTn0MJZjQfP|$5;zLiiF{OV0@gcMyI`)Cr9}iCN4Fx$X%*Ld z+!1hhB->`yX`ma=3P38)V0UlqknQ#jIAj#*c$#U3_%QLsC4wd9{lKd?u{-jfzhmgG z>1q69X!#Bbo%$@Kk5{yab+&`LgwR!McM0SJz*W!Jmz+`hY|4y@mq?Xa4e_FjfY^s1KMgpM2XOES9geSsG#{RkP1Jn7BKYpG4Ll>vYQj5;)P-84rEhg(c zkpAnhSAQwrzxOy!5y+29T~HHFhhS1p9V2)F-(athJ1FFtndIo-bT2-r4+ugx6#2}K zQ}2enp3=JJW$+%|{y5(`e-pX`IHRK4r>>S`Z^{9_SAU}U239N$KxWI9eXxDL{IgfC z{FdS~&$_E-KS9()C{MrK)+Lx5a~&4c&hM+wZ2Fru{!Efdsd};|O;}GB$Gb38=Rx!6 z!@mn5pv|@(o?(S&ik}|phR&-iQ5==MPrdh1AwzB|`Ajj@jf*arLsC?U6pbudU{-1( z7;+v@a3l%W+(lm_8rDLbMtrA7_f8gIxbeWBg5BN6U+Y&)(}ON~q6nyPVfTa(vXHC^ z2)?{-t!^8GYY^L@o}uc)bl;kKh?O<0{mhR9R~NC=w|lq}2MQX(deK7Ywxk(N7Xz4Z z=ftee3Lu5ZXq2SCv{^6qn>JF9(k1=njXY>x*4GiY%SxgpSFQTGs7q;&?Hdur0O7FTcs%9_{JVD1?>xvqF8H4 zy;C?rfb)YOJ=FLef&>e$MHr!~aYV!C?kqpmPk{h6>tM9ouVH$~nZfYH8=QIXGCt|W z4!~(EJr#SUX{eDh7Zj%1km!sYa(G+15LqPt>=m+&V2a6unp>KXGOY-)^J8*w^hcBy zT?|HQG{4Mk^c_gq-=)P!a3KUMIjMZ|aQA8j>JEHOKRXC0ZbhTlxsqkyLyOBjb~eZd zxl+HjmgLX+U-4hw0jUFFeQ8a-=`GMJfcwzSr2@g(5ESX88h&L7Il7^KCMe&PZ=FCt zAh@6H{TcKKU*=GkC^xo3bcGWD;vvuDkH8NnYxjf>#bMP8xvvSz`YbRL%u=N{2 z2~7t{13tHMQb3%=^bKOqRo??U1C(Fk?wF~iMsVAI^!G$77^grjKBCo(md>#Z>9On5 zosDQIVIm&_HNfD7O83<=P`ZKUKs){lrN&jRI-0ZLd7jqLQS-D1(l`m^R)Ep{PtzICp5%;V*?|Kf; z-=G=>l+g5C)<47a4h-R0puRTHmVq0Zlm-a%R=c*_9S-P;k_MGUvogQ`!ZB`l{g8N zj;+ck@+WWV4au{1MBVl}D;}^6v-7k;~YN9pb z`dbxN)S+)s_v`S@ZT%$isGC5ZHfN(y={?6QLO2Ji_rJ68>!3Ibg`RltxQs~kM&3g9 zHDzn1oNBGV2@725)%p<|gJCzqH{hu?h^Qb?l)~JbVnlugfNn?tf2mXB`-(qVO_TIF zqXhGvA5s`xrgFQ{`^+Wx4?WER3ZlurX&3KsRHc{edzA6!fI+3hxS7iC2Ror$38?A) zge=anCHg?TXlEyj+Chl}sOta2D#u=4FWE7!u)mGSs5Udn`bOc=owxZhAI2Gd4~U99 zcIdY8#5wO#*6K0BE9v=u0@8yO1>)i=6M}ZyW?<3_Fda|{wx?b<0ADyDJcBmVQ8>^( znrsRTW@;Z5Xh8Q2m)OrMV#)Q1h+s=06^XNo)TXM>xOty37A-bI1#0_@}WUczqyTP-!8Upk!`P*q$m){Atk`XfeH)S_a4Tm~BU%Zq4}`)S&< zbfWPrCbACUVq>Xht{WS}a5EcHfWig!@3hWZx>tLdm$+y)ca<JsZla8=d}Tgb_mF8Hahiw z+*KKT{JZ8h<+jysa)cltWZ}Vc3iEKv1#hrXtijkGI6&}A6J;@ryro~FeQOs#Kyg?M zZcYOJx8jrMZnckqol?{w==gyul%!f|6tEaE)p@)60c zAS>)#Ewa5m*iIi-(%1gDzJ|Vu`@+tjS+?48&^+<(((5R>*&JZSFl`CIkhEQ*_b{dP zZ%9Z(ZUO|L( z3Qt>G&5J|d{Hi4_(AKG-G152`UXXtv(8l}jxXxZmDiylU-`pbFK6_X+&H&|a^*3oC zIsY!k9vZRs1D{!|zFhC&EHG}=7HjXt(PTi4%zZEYZq?n*d z9ZJI1r5#UX*817!do`nVkB}5suktPzW@0D#1+wj!+|zM1Me$!|daza;`jx9nbygF0 zVA^I^{%nq;1V*_PnqWUasB!g%?gHxH^FjLCh)5Pt)LE4@d5gHx5jk|HQFvx(WM%_ zgCd^TIo-|9-dyfy5($qqH=EyH319li)*sxb$JHmp6I$)34S15n)%BZ;^;}Ok z6dcbBSKa_!GD48~jFy9d1n-Q&yNO5Mq(;&oa)2q~=RfH~69~)wwc}+euxz`9uk*2U ztV`p4Y6T^{OgB`DEU~r#^A>rgP{@l z6LV@zGANe<5BR@|b<|Y{l-V0ltXrdzCzg<)%r3J@4sfWf1T7}60a5P^Xq`KQ9=h*- zO(x1p9?aDE`^PQz%=bYu;S&H-f8iAK%;WLtQcoj6>vN;HmrUrsp}M-v8Au*B;<4OE z-orp;zwT~T{~z{wImQHKZ9AgC*=f*!*u1>O?x{atzD&;gnPiT2=Vxy*FgYO$O`^h& zv%dpmU1BPNdf+xZ*A7JFTPs^vP}y5T#$YsA*y;qD*RqhPAH)jy+oZVu+=l9ZWnuO-ktQ{cjKGU}QcOIUAW~H$mKF39PKsqM$2x6@Y#_JxBqk1H^ z_Hv+cx3&<`6b)X9eR;>CcO9r?;JOUo=n2FqE&CAgRlZ z_-q0zVfTzma?T7jAWKtj0x)@wY}aKBz+@X`TTX#w=tfToqj?aMXMbq>;v-;(KNa2^ zMI!AuI0KMe0zOj*;#*rCSpUxf`F;`8YjEwg)pgDsFEv^UAn)PGSs(8d@toQIG5Bk! zc?X5^QgZi-@IH@tXZKYih;nNXKo+*RU6a({e%Y#wFGU+O z-tVMR2mIMWViG}j;61+S4G59L(E=$zaOj7CpIwj_VF9FZj@!MbD?OUtuNRS2gy9_q zG_$oDp=Fo&YK!^ciZTv~8YStsHT5D-k^w96e%iocf;2l~C^|Li!9QCxa~gNL5gc`w zZw`ddRZpi76L|$%5qgnT5zZh=m_ubGmWJ(omiilQyo!Yr#0LFpv){s5z!eGCh1YS( z*+zlJb>v}HW9JBFDm0<8k7H$P;Gud2vhw2T(7A>q*uS4krH>i9~ zbx-ok@BJ;-8p6@XbP&(O>SmP5NbVXQFEVBwynI!WI&=n*27kMg2BppHY(XX>0buBCTUHCG*MbmD{Hcc)P%JYSVAq9T zIH_3@%bvz>rd5E4#T12l2dN!YM^4rw>qkeVWn=BwCz~1-K%w{VhtP#Vqv^oMMq6Jw zR{%lPaV!Om+XfA7$az`T3+!VyGC=3KtPnMK3YwSw$12HA4M-rPl;y61iMvUD3^a;4 z=Po4^2STSVkRGL(9J#JfEi}*I(d;Kx;6qzB*Z!P z`vnN~7)!6~!mXt1(ljHo%zsAQ%`~hye4>5t{Or8&zwPf+Hd;5Nj4<>eAJa>CC>I)( z_T(%{*Bh{LZf@;BiZUxckgpTj?%mS* z;ydkWjl^Oc!H9Up_=aGxa-S9e$!El)fd|kZMb+7}+s;LYvw`7R z8G8d}LF9qIs(W2}7{!>2p7^(kh`;>(c~aUh%Hq#e83=~@ruXTcg6&dD1#Ny+3Gv3Z z#>hQQ;AQb9XZ~ZYB8qKNgh+by^`JpebZT_AUn69a;ugx+n*y?fh5>R+Xro|(Ae4@e zv)MU1Qv>SZLaa1H(o<#En?unz9EIBfJkzD+uLQz+w-VMHV@<>2e7@_yc1%bjIkuZs z9cgMU0*wivjDcKDGQVTlbuvyGu~yA7%|s1a#?tdI;Vf9T*(FDz!b9t|oXqMwNzg&= zdAqTM3R(Ok&Mr26j|Sfpm>-bV@D)l;O*0#$1A7#sGz8CB5Eehc&XQDC4qf0T69bEO zs$>CUg?Es3iY@mT0Hl+Q-Jm0F{^g&JG~}u^vK9(_!mcthZU6B~@fZ6enJy*#Awk0c z!Bk)r^2xVGVcceIMfC0)znTvd6iE_tN)mx!B1jE`7Es?xc*4r&h^d$>dtt3Jko}Gq z>|J9IU@U@RK704lFD(0D3Zq&|4U^50qd@$v$-ctJB2bj`&pLadUJxbF6{-aN!mTf_n>i%N@Rh)0!aRgadUZM7;;+D`lxU=9v#%2ysv`IXOZl3)Wc8otx7d zzZWv^?mxR*&{LH@HhcTpD)5p97QLE_in*-bBm>nr#95E>K=g zrtsrq+O~g1(mG9Ex7Cw@{MfHMR2Q<4p=U5aKKFWj5Q-}bY_lbr<;q|dsgr<#YFzUG zFz)^TfaZ?J!2aj^-hg9@-hWj9&kk~$%^I}qrQg1U zzrwatw9bf7Cr;judkcmres_HJk){x-Y#7o}09vje2y~lF4`@eMK)m{17^q<_qAyA! z7OjcjDmQO2oq<6&`?UAp;2S3)6Ar8#!3udkeEp#0wI!isxx9&;12)X3~!uz4HzkN{uCR6yAjOvQ_^btmM(Ap}Zgpcqp5)OFXD(SXdz=W5><8Ut8+qujcu z--Y>S$p&)J4lz1*2c|~A{H9ra!3t=qqcb<1_nhA0U5+51MpG*L&JP-R2#XyjJHTp{0n@jBA+|qA#Nv%2T)1w0*OV| zQZWK=wbDGaJ3wJ}=aHqsH*QLUz$5;4!4+x#nUOz+HXVfEp1*egc)_$&N_-`K3qbJcY|h+M?F@b%Df|$ zdK>V&ZzcUah0BQ&E(XpRp||l9EQ=E|X)Qx$J*qhcu&s~16h9=kkAKX?F-QK`P7}4v~>CrKrPBIxK$bE!hIJ|5TIJbk5^l!s>n4}a08lTYj2|c zYdp^kSP$TRguq_+YdB4=|E2O)Nx-b1B|OWjG977=0zBl{!FUCy3LKA5wcQF3ZLA6J zhDO#STBRnQ!K>psNK+KG6t~U;T3;0F4*Mm<^|Rw$-X?K(jrp2L`xpO>$8m;6!} z24#IERePsCRU@Qgtk`#W<(}NZv*=7U@Z&=&!Zas+M)&6wQZ+i*=0Uu^j511fg>@^BP&;61X6sLwPM;4|gXtIrM?d>d_M}6%9<0e!r!6PTV@y zjP>Bw!ey_#W&ooL?U7{(_X5c*>U*XwQQy4F#*^t%enOUM zi`Rqc7RA~%y5L~md>%h0@K`UOT1 z9)EK38W=+0zgZxxr~~Y*XKS);Ulb<=?dWdf2Hi);Pd*vFMMLVs-0-u1=2yo{-_?H9TIs%Vb0xoB2m2aCcmlJEgeo-UZ zx_GDogxyV=z$C3m-<0#?5#!QF(BU42CeOX)nk#B^rB+}RQ*Eil+}3+p$y-*<9T+Db z`7^WuqZAL|gi`PBkw=4fg*sK<$T|%)+%nwK4bNDKk`Kyq*d3)F0k&pYrvv7u9cz5Sag*+`8yGh-^PVEDfTd4yZFP2by$XGdwC z59+1HMM zv+GrG?e-Q*4`a6`RR=03wCI&(>6yY@Z{fgZAPH#bv#5qH#G4sx&<=0;ou#4HRn_xU zsVW=TXa5ub^9XgM!PUcavC1uBLQU@jb7*mex}>P|u(#{YraUmkO7lNtO~8pK1+ zw#eVbFI_$I@?+8*MQk>2fI>w6AD)@+C^cNY2+8k@C`eVrNFY742kHV>Me5zZ*C?$ zBMCQ#GyH?P%|B~1HxCkBI}fO!M)0%Q{MY(S8xzg^ zgX6%_l-9QMz_}D1{v8NetL%*8h1N}_)LX?kzzy#K(to@UcR{mlSoYpL-vfgJc0)}PiCMz>qo zP_x=Bhj+d0&$*87k_;E#1y+fITx|Z^mPq4Hq+FBgXS&#wToK6wtVX*}A+}hu%DX(8 zp{cOQZs*6suEhRhrozTwA~eAHfkYcc!zY}wr=roh)zGC1xnm5j6Clu^c*5{~@G-xf zxCYBhn*-R$A+-cT@YeAY)Y{#NAXSo~w$igM!_&w;r{K`xL8E67(({M^F;2bE^c{YN!8m(Zf5pb+D2YT(n>glk!ccAW4b*Rww zX7cH+Bh6fY?xt58)ciX5?zB*iVtOz{1|TC;!3Q_olr~VpCtP@v*;Pi!xD9Bi-9^N= zHUgtvlZZjfuVGa05pxtz?F;j@H4V0(j^OsoU_kS;xU;G?h z{KA@I^h)#Ai=~Fh%crKR5JLj+Fg5=+-~o9N?>R}Z<8cvZQD_1j_U`PyA7PE>H;?=Q z8KDBxyXg61N4OI7`!;Ubdaegj6x(Xj-+jl#WHpGhs-^0Y( zQ)3l!_DFpm9OUslfsy>*=K{PLfNxk|*tKv5t-B8byHNTE-C0#I@B^k1oQwbYrl8>g z^Ec4ghCZ2jYB`>|9nixJPx)|cIboBpx8y!~^^^At9tqVL#u3V?;Fjjm8Oe{p!KDT` zxV(eVC95Z#Um0D&-= zHPZWfL>#Mz0yjvb&!@J3|E9+u!eFF7ikzIW_6XDDi3oweg zNz73a)^P7bm81-8AHJ*9rBpgR(q_~O*`mI)k<*4Q61O+NMFPU1juAZd_^EXi1@I#@ ztn^2qpTI+<4Kp*5wy&2m-11sP z`R3*$>x=8N$mQI==`pcK73sVQouD62HT_rxOM^vXAd8?X*hqFiCrCM+v#Ih*1XEw#(2{$)Cf~%GigvyBzLdj5{N)SxVl}EaVB{{H zC40#>odLGsh1>bt&mFgCx25wuMwTcafR!5jUW`g)$7)VRc6x}^<6wJp4C^c zFjp5>M{UfjFnyV5YWc;1|`1VKqe?lLJu6gS#oP}Nnj6l2JbLRQ1 z3I0)$bPB06c|Y+s!esS@hb?2pm0yfbW6#U(i}TozozFF>6=^23cOOX9yJi|f#c90g z5gNNvr@I}hk)lhsvZi}hPrX&wP9##UDO}X9!ns8IzD1a;*0>0<>B*LJ)w5dNHMc~A z`^crszr@z=W_^6OL=3kW*`(*olx`fOm^ibv0d(K_wH+R^13SGIOj=P@Hzp`c?Fa|c z40O^Fy*|o{%{pG2U&Sg#TIg?t)_GiYG~~S_rC)AZB;kFOz=-`Zlwiz0_o2Py5#t@d z29k6ao;>O@>0jL`+^4@kOm_|O@9!7L`bcEB_LJuB=u)P|)<*>Q5tS#VD9czu@dvhb znkI^83N1?ayM^hdkNtQRY4(y?rPx%{j|>Bq-MaL}wKu&wXwAzuKQb0&JnX`S6&QSX zR2gD}g2S|UM<&oGBEL}RDU<&@7NsiRU2z(BG5xan*Uc~Q_ywIuYaJwBa&*#NEJnI} zS)QyH>=de1jII|lW3yeKu8m2nN|HpUTx_ZW&u-~my~`)JI>J+Z8LWuz!Op=?K^9ZR zxU9%hV@#Mz2U%dry+K+h!oi|fXI&z&*q_3SRx1PJ@3O42O!MlRTv})^afWwZdB(lu zRH^seJ!ERPdgNetFnaL?@1}E=V=GbEw&_J0s>%EYv5eH(xjR`O8%?}6#e0q&tD285 z;*R@&%$iA0pt_d#lxpLv{CoP~ZCxUIoM%T6bOr?mMH6Cc3QFn87tk?q!7v(Mo7|ST ze9X3}ew{Aw(Wu>?poZM6jIOo4QZ5;?Dd0vyG7&>87ffoajUD#!*q`yIMsR6vB>cf5 zsjY;6Fsl>6imq>?b6#Yg&P&FE%KO4o;8iZ&P9kUL7N=lQ=b!SS+XN;`&M(CXX*tJ$ zpVps<^}}_q4czJw>}^C5YJ}St)!tVHZznZ>b7+>{JkqZy9@s*xxP5alb5k9c9O-qe zTK$N=k`?oz{mQUAIi1`rVQ)D>&@$mJ+@OBOnfE-- z4T{?LxV*(l-|(U`FQ5NkbEby_;i6eWWuiw*{TCu_(!?{$(_ZvX+Ey+IO`+xt4>z22 zbPW6*POc}3jSR~_1B8Q8rijkC@dvAPE)ja>ZjZ*RHp_4}wG*u~v1Ko-9ka#4pFu}b z(NOY&D1S`de%ZYEg9EKT`y!%EX1F&(_FzdYDY9JTD|9m1hOUG6Ja0-~F6EhXKmGPg zm>HI4Xn7f`3<6yqya|H7ej<}9`bm_5clxh5MCYyVN4qqwYEIi1X=7p@D4^9Q|`tp44TYLa})?wPZqdUFe-Xl#T*10XfeNG}Pedb8q z(B@7^OrMW>W0SwA;vG}3DuxN$!k)_-dn#-ge#6R;tD4-mTa^D%un( z$52xRLoWsO&O4kcLrr#q!?zH8_6gIKtpgH^(b}asiRT8>LTfLIBPaYeTIav@2MY4( zABlS}@O`y0ob=wwrOOLJ(^I>grW}&sF7+_o6XWOfv;QPu*7B=8B-|sMZ!0z>PeXaY zy!4AU@9uW8X0Pd?eCI8Fb-5HV_Bpx48+0I#tN&Mh65(xrA)S_LWEo1s?n0#`Dd%#Y zP5nhLWfmx5BIF5kek3(!rJaGIPY#;;*Kot)u4CJ)IS6;Pyk2WhcCs{5ElhAx{Zck@lR-lg%n;v#_qDX&#g zEy_RDMgw(Hw?0UxKS#Zp!cqEjJ$>5^7?KT5nM*!d^H?eR`%i%)ONtbZ(rs{BI$kH{ zYI(Z2zvHmW%kNcv>*={AQ{O{*@t7g0^K&<~y1ElXM)T!e_jOIh!0%M7=q$IZNR;0= z{QR)F{7wS9XSl}p&b^gy+FCz-%9-`@+4?9=%Jl8OxgV3H$h6>@C_mRhKs(5#Fpk zBq(H&wrfMhC`vP5b}#f1o4P=YSd8e_T4ju&kWxnj>zuCGrI!=-gw(Y8=~?3Ta)&GP z)!$F}cU+&k%Ut}Y1Z}4tRsQJ2j-}^dq<&L1;Oh|?V}5)!IFxT~CBw$-V(V?ZzPcCf zV7^5y_VwnJU*Pgzl<7Tu=L{`B6Q`brp=hT9M$52}L6l}>&Tm-YeZ#GDVoR44y%BrP ze&@ggou-Lgiq|VKHB;K;>NO@ciU$Mph?rzlWgIH*rOZW!aQy-UlPR&aMYXxR8l0o= zu6v?2v^KgtmL;5c3k(vwB-RP@3Pany?4%h&6Ro5B_Rovz@947Ym2Qa}x`kNPNILY6 zTT>8ww-Uew7>GkPSgH+f#fZJ#ltiKBNB+5?sEv`W&?q ztY(LBmxFJntUK@PZ;h2OO`mgCw4rC4Im0LUKC~^AP;7~=co6(>0^67%XrZqBRdWbV z-~kKyh(`S2PV4xOUdv~kg>8c(sH^D8kin(;?)tq_yhMUoo3=I^^x zb0|~|`P8wzxC^o{$Z;!LZ(=9FwR3(D_FS>I1Aa5kPt=4Lse#kg=JNz*YAquvb(1)s4sX@z4d0%yHav+1Qji1C%J27 ziH{-{MA6>Fx^uCk=NtwUJOY>0m!a48IKm!DsMA6F-uD=6Hx?bV;LE)i66Mt$g%dG7 z7aG7bwVdHhOZBFQc{5_{tUK;v{)62vz0IimogsI5i%f^U9911Y_%xS3H7X!bd6j}q zW75PPWzq~)p@s;?Pd>L1EP7N9biBO6uuuvoWSxg;?RO8k3YfiAgA zf;rt_s^vpVLv>|^PstHcg5R-RK8Q8rEL?S&-Rq6Tr!zc7_EuuRXR5?W=b{qiy)5@wSsMLJ{K_XPaeBN^SdlUN&zd%-kR*vm46;Q-O1h z-Of)q$($gFg#1~GzADitVeJ+Y7t`VNMkmp#ilN>$vHYM!v4YEfO}@)x(O7(_%Jo~J zA8se+44>ks(c{f^bZ?#k)%Aw;?c*g*Z+eKelNIaWPejCDS*=d`#wbks^5 z+Wt;OGCWV05ZPoJ(VXv7BnQI8|LRV$YU zl8c;LFLW8o$b;_fCWVlSg;H5iv2LpaJQ($j%V6AcZ#K473!b*Dh%V_$z9fNo{IPn; zGygDCLSpnQ0Zvy-SpiSQu#MU%rbpPqxKMNC7giFJ<7Pnz3W^W=~Q?qZHpPQLwe3&^}hOA!gZw@E?Yb1HTG#O#(ZL^DVRrYs3kOc2F zWmPx;*??0OgD=+Ci6GEh(!NxPG#@+GeOG5{aQkn5z%@Zv{mKFqk~x$8vlN| z1NlT54_>$mU!z@-ax>L(r8D`<9}l3oh`#nsJ+fzvJgRS-IaK#il7ybPpBGhNh_tZ` zf61x7pfpdvk{mj;8!h6U2LF6CV;*)ya*SB19~`Xv-YRL$Fr&q|$0^QIZ75woDaMXQ zd481hXu>)ZHW4?GupPYk=sNLp?bU={#;@I~=%=|%zZQNrQR$oLV9wO@Y1i@UGW<=k zLIYq=d0CzJBcbi3r~p?6-x{tZ2|Kr6c7jKKA4Pp<=1CuC)uxS$vmL&RiJEY0DUTF2 z-Z5ot)&+4U+VVbgQATd_PP^{}fw!z_*kWRe!pH3$?9FT7#l;rsL3S~s_bNJ$KMQnd zq%)V+FrNH%;91FdAViENDAPl|VN4uv?kqT5^{F0+{A9tvr)cP9m?-nvzz#w&kC06H zr;90DxwQoVPv?QKa$O_qPPjJ}XLxeZX%Q1_X=>fo6I(j$-QdtsN2O$rQgZSq>iVwY z+&<{|o7QeRe;E6JL~Yc&&7=g=Kf3M|Fb9tKg01PgWs;T1n9gS_P@;bBPN6}GvZp4G zM{*f6Ll~%HN@~Wl-E>a%pc(7jvJYL$Yw1tnj3yK)n>sv%5tJqz4a{q!bjlZDe^>To z>4hWVD%Bc(jcEad^7eQ%&JYzKcc7m5jU#cy8WcLYwkJu)>T<@6?=dv`p_?^*%%IP5 zo{b!w7yo&B7SqW<&Fpq_FIUeoZ=}aJa1VME_SRymmr%%{J$xZ*VTHDp96_sql35Ky72ef!aoL$q6K( z8;6N&CrLX-X(HMdmgQhuS}Enq6@q<95{}4{PG_2>G)zQ0sn+?HpVvXVcuhA51}|xw8ua9Wbac z>au_b?=St`)y?ozt+%rG?C#*VpeXcjT(*^-DMh@m9^54`KYeJHr_H@FB1*1Fk%b%` zW2@gRO)Wi!oqN>kQRN&VY8qm-H9zq#NN$z&=sJ1ag^@zopt@|QAGi6*S71&x7dkT==$_}Tf~XuB z&G#<+67y}SnL7_H9Ib`Lx+uqVoh}J#OedN%Dr^+%JPKtZn(e_p;>aLTa5uTw@ozit z_Q#)_qT~`fMpQ{Zo8Gin#>~mL>O!49?F#@a$(3r<-mqUY#rNXgIE-MrF0sv)4ap_? ztzfO9FfpX!h=UhjaUt15D^cnRXyJ(G3>K3$UGiXr5uta@*?IA~i50SZe%`gi^BS}r z?)d4Iw>)Q8Pq;7X!sxkVY#hZKr=fxiQTi{As%mqYIq0lRqs_e{5{A~ znC5MEH^B>nUbpi5EHeG>Gl&iJbuZLmS&we72;o+S_R|l8TB9A{iLSK;+fIkL-y4NU zLvr-t;730D;;sS>xhA^E4tA7CRia;>aOSkWBv`xMj zWXmve1ADA7epL<>num1)w!pASJIB{sgIr3h(rHu&hPc2zR5e3#GzeT<=)w!<_7q@dlAn)gx!1>jl~K!m`7`2 z=p&$OwD!!qf9+0ZxGk@52`i_6t^ipsXkt#D>+DyS-59svNzjPYG@tvc8#p14}vfm6O)Fu=45lX{#) z>!58`$S}Nj7gH;3Jw#(4F&V>~c)3M1>gX{`Ox^rEAxuO=sF2psH#|{$=1sA5_Bd?`IZPbW&;V0S4oV{T>Q=vX(zpGuXax&De z16US{L33gM$gvwpMt5@}g{|_%^;W4#)#!JG=)fdY?tX|!`1tfKN1%S{~MldayYl$@LIafahp zqh27(Tf84I@J-W-uq}Sb9YF6BBImfLFSp4r=A>(COYDwi*@p!tSWrT>F%R?y14Z&T zgSIHt}w(378KeYVbr>HX7|~phaUGLiW}C&W3)djHF#fQ}{fzLF_74 zt(2HOTdSguUN=BJNe=Hv;}Z7Uhylv-|bTQGD^)6nv%Y6&o=wxa?+`nNAeFbe6^<;_?tGuXsRWbwKaG8PJk*gS&hFBWNu+!K3#o)E=%tvOzpJtAMb_qs=Ay`7&>gDFiz$L3W4Am z=*%kg@7srIBH4dCd12J(B_b1#?@B$_PkVnZE3=)+5~ea&Mif&$s+)u}I=k+jjhvGI z2qI5AdA(_TOwN%(?Tw#=UE<1p`tlc+LtXI4hb+1veeL9(BUI7mVIC@c_y$nnk!aJp z?c}sW)T0A)E;PHU&394Fx`9~H`_ji7y%WXa=!chzqm}o^C7Ci@H?(psgSYijt!M5r zo>hmlLJF8>R}#js-=AwIUcJ0FQ`gULHdGr|=ywZ6yu zz2o~{Uc+wb+qonP`s z=YEgPI5cK?O%0NA>_y3X(g_8_ESIORCL2ik7Z2_1yth6gs1m`*)+xZUTWX{u9F#x6 zo8c3Mv`8eDji1&kRtZXJh*l{*YxD=69ReA7H6e6Vg1DL~*(E&xGXI`(TzHsXz#7>p z%}1GbyQ4T<7j}1%r!JeITIFEyM|T%{Xj5Tb^tAbQ-6vk9i~BVA>{gvEt#W~*tD6ptEG zf}>ueIL)IdmsGYbF%5(dm%=m=YYG2Ly6>7S$s8oKC@=SLv-fuf?fpYiwH?t(i!`J= z5_)!SBqg@LKGODtuJ_34P1dI9hq%!LGzYQ+JidaY^_q6KP=5O3g7u@>RI7~k!2&0a zopJI7Pk~1ZIxR1t%F4QL({H8kS#oyWM*}FOn_YtOy`98rzud7o%s@`RgEjMZlR8N% zLtfLZ8opXQqEBf&KTbv7kP%+avHN;GVFzU8eUY7WRkM(l6;a)mHSqnjY<&V2!ho&e zzfk1~!1rnfQX8S5HtQMfAB$aI(YHojR5XW{IU(V=&^njoTkT?HGoQ2J`O7ztvYkCk zFi^BW^l_rQw;#zj=?5J|?STg@FTaH7N*fV!Gh{sw$#ZZbKv@=ulTB0pyowWVIF|Ko z{vPKjEz30AMeLEWo|n_Gdg;v}<(Y0bo1oP2SyP_E33psFhB+)y@<}>&u0>wSs~jOnN}kMA-F)b*Io2smlT8LGDom}o41>*bF}#3O z!`TYN$9~iFJf3TE^1m*0)%1$_MFhLnt+4kL#@8hHk8sC;ksMh?IZCM1&epHln{Ob2 z!{k-5Wr^4D`6{oY^w5yLj3+LX$TZO>BkrZE+)~*8vEZX2;*)dY#FJ&Nx4-jVyib2| zRro+^;02SJL;vq}Gp4sLL=5aM#h#6GOq;Cm8_4*HxO_EJnH)3btRiRTz~a+)DlP+v zk|8e@m9uJa8JE)y8+R)0|^{f8xzFx4rK$$=9ZF+%sJSry2 zlnmDwKK*s3T{o=5#`L5oz}zgwo7oZAPo1;z_>|ng_TMOrJ^c$yntd{)$rok%^9Gkj z4*J6W-{fXzFs*(RixY=#S>X{$5j-?Nez}XHn18>|#B`>|QHaD#+%8n`AR$mY?-^p# z{=FxdxcneLl0TOX)*Ee_+5cX5*)`_yYT~OH6Ugcm(iu)~hRcw=kxEa29Xbq4(kIlkc>saQY{N*k3`agaNOglkT z7#(z*VdQAnNYtyaVvFheNf&D-ec_^nFIbH}DhlEo6rfJKj9eOO)Mv_k4;dEx$n{?+ z&VO29V;>j!)mPw?a1keU!eF-B)UWM&O6RX_(~{Fc96#O5_xJ~HW}+gil4@)aXVUx z*TT}U5$6mU#R|puB)j@cE=(EchD!MOQJ1z@bwTSrFBKQFi^KU`JVDIjeOT};gb?y$ z9QiW}zd}%381|o=h_i`HQM*`7XDZ+EID@3fO3}crh=v`~ma4gVNCQ_3MA4_4=}bO5 zS*WEddUyFk-d;wy6@q>VsJb?a5?IF*!_5C&-35hms`E5~?#zKj-r zO_OW=n#hfQy6Snjs~m*rh%|^W+8dWLhyazifLxcGl!%zxx?yPkL0^XjC4oT!oih3>R{#c!dL6V`%dQRuj9y9>Y^M^DTxhF{_EI zBF6o@!*_m5T0i)LZ7fwI%xPPTNE=k)1>(XKJT7D1lAoz~rCme|K7(8~XzTyV<+smo zzRN&2V(7k+4Ti%U-L@9w7dJ^D;*qs4ciQ)Oz#cNi33$SiH;Xg>orfduGj3j z@tJkDBMqY(VKj#&WNpo5X3tO3F2daHs>bv( zDg@L9vE1DX%5@*}+&z9}@J_|4VkIW?qO*A>C%M`jKl2v8g%Hyp4iURCU`LV|O^h-9 zXOZyD9j|{OG@8@_T_-;X1}upvplyK1!e_2@!Ak&yRXZ&&C|f7kE;%lCA!Mx0f5J)% z|8wbh-v{?4V~oLN?fx{qI8)z-a09xL>3e1F*{^-pP?E{iy%L$pPBqB#;@dMQ4yoJ9 zBc&##V7K8a;i#cwyJ;SjYs4Gd%D|zsZCj~WJhnzLENa14;d!`Buztqr2yXfP3%Mpr z$jTBu-Bb&IsN>d|Uxiz)A5r6QCJk$-xNxrucvl1n`Ji}3p-&Ww>8Q5}=ReGyGyi$k z&>)NWb_!7JTkQtWJMXXr*t!c=yKAKVC%AdEmejHFImr_#8iK6(YaC`rA#=mdaPPv- zGz;5YUAvKsj^x57Rpy7cq1rCY$35RFIHe7D62^0MB@f!XLY@Nmb~pG1O3Me5zIE?# ze>A%bUWF&T|HXYrsorjCYyxt_Y67^P%yOJj@!Ef)rSYj-1o3*3q*h$?%c>6Nzp-SHb0Qn0LaNV9(uXGzgpR~@0D;y z+7+J1$-*5&M-NfLt^5B@$%_k$-#)i zzwM9qFv8Dp*`SN!=<6n7jKMc%fB+<7zH15R=O{|LjJ!w2y-wWSUpu2Y++kA%J9Bpo!UJgcJFegh;QcG~1{}<<3RWFiGYHl8wW()* z#oVx&=Lld%xX`k;FJ3`cxkZ-aBz_%Lw4AbIq^EbwY+VbK21pL7i6B}sTGP4m8!rD-3M4 z%i`tayBFb}{Lhv1Gq~!d;X@+=be#lluewxhf}H8HC{7b3rv;7uOG_sg*ViGj_4YO- zwn{IHN{~x`wY2HyLHscWZgz)Si%?GW%>?2T&Na^2uW@)~amc%`P}(&M#E*PlX7Paw!?_n5>m|x-?49*+M7IGN2Zo7Ak#r@>)9rcr=r_h z_g;+^BUt(dj^1^ey1gf=nZ0sEjvZOFEfU*ll^&svmqDrbVJf6vIQ*=uZ}a<5)O^8C z7!~w4>-r*-p7CRiclw9#q$tf75GbVJV#}0#`d0>)UX$&nQS#}{e9ku=Ce{d0UTmAg zRPJZ)gMB;H-e~SLB2F9fkjLl5&SP@NC^vqV-Y|G;$md=AOO|?!$lZ247*kI;$qd$$ ziYLzZcb#dFpNH?E-yiJJp=O-iv(SGgT{>O(iJENG+e38W4BB?GKL#aX5-$l`6hjhT z>r3B3w+BA#%&NgbH&!OvvVUv1tAt}i z<_^=w&5d7N2rCwN?gxu^6z|ho2t0sjvvQo#SROGty#^$sUE|ZAjScQAs zu4%|U5`pdPs%j1OjO$%x?jI@uB?QX1Cq0x$$vQS?RY_8ySJH2`Q&zj(wapM@7OWpU zGaNZnn)C5;!uj+QZ7FlX*{-T>=2G^BJhRc;=8g6?x}s0A0HDBSy~0cyU1GPB7Rq~% zhN*@ad>`vk9YA0rV*#Cn+06cZ1(b6khXwKk=|^l7N?GOgpAC=&D3UX`2?yNjCiV*p zPE4H5g9*>o#WE$BKFz$B$v5C6MEFs9#$9kzFO~iERN0fl8Q{l;AnX{|)m^Xe(vpzO z^IF{Y8`93Iv16h1s<({r!dE=eQRgeqe#D*G{PmQotTIT!*Yxc5PRBou0&4A+A0K|Dv@9P@5{w)HmcrTM2~-DVZggoYt7#h zD;+bpPx0G(ni~TBtTB7ucx|!b0q~u%YJFYhsJz?7$C$KeGhk=}Mb8fZW71-_ToK9< zpTJ6oj^BL=^~Y9aFq>)%x{~n;-+%Pzy~QO|L!!kNu!_q znS_Y)zgJG-*oVX=^@&*+idiN0^e2IWTZ3rn&R>ACTHIG*7a;n1rva4jOU#7v5L8I{ zA4e-Up^P0{<-#;+EiAPIWl3+glwBA6nUqr%i4WCNkxAJBo*a5~|} z+r)+@BdX{x(Gyg$fD0<+03lhtg!{y%dbi7H1c5sJ_#P_!eH+imm)p$0joi^9x)4^C z->~6GJE}+Nc_t6iRIE6Q9$Q8*pw*egSzw&C%-;gx@J*s#hmTZM!PGH233ZGSBG|7s z>Nifu#EOlT`WExqfgJGz?YL0RI}=^}9ji>64BLdtTS~1$oUp^Mg?Zs)asAJA`qL$n z@+5qOP@bhM8^od$4M}!|@W`FJg^i;h0u8JK2vk4M_qrgLCad8~(oB}25>FmF~&<=7;g_rK&|kRUpd=sO*%S17o`-@h}zSJcpE(999=so&Ee0O!l76Pyj{k*QS& zd_!5uvo+yw_R}pm>d&-rC{LtVH7jyISaelrFjE7DMAO#>~65LwqfzlU`R3s(CY z_o;2ANA}h5xN5e|MuA7PZ^(VSt+qM%^v={#pMfJpTT0M~Hgp{*h(^>XwrUfn!xkmz zT!NP=Wjb=qpvF$tBPGvL8dZz}0Q(t_$rX}rGE_}Twd!^5>0zOBAE8DY9bfcC>4Fie zR=xH0$9Bz~Qasq%DmI2M9MB2B{1pi6`v#qVI?j8HP8ZtQbbGYxv`(1oSr=F*JXL$3 zZfj9?R+UgsDPT9gi{$Cjj?M387YyVtD(SY?Y!+q&L~Ch=v9bXog3B`kV1e$IjENd| z(;3&n=F<)G9g9izQ^F>d)T+2KMb=1NOH&oLg}H(!`ZCv=@e?r%neV}ywTOuLT5bJL z_3|uL;braz1D)oPg*U6uq?!=U@Op4p)n2T5c==tCyySe~`*13t&%6!*Aj4P8hZ1X9 zgeub-b5M&QR0awvC zj(Z&$sVx407mJ~VFJDc31GT>fj}mkr`7mT~nU`B_dOQPqLW!TyIz`5$LbPVvBF`Ca zPn2@eC_CF)=MKs_LZTH3SRg0gkv}dRp`TaJBWo8j;0{r{{Uo*17L zT-Bc#-Z_8N->jiGVVe(y#-o?_k)KA5_(n8ewQXTwS6%z}0gzc8=AB=-uv&CyEBH~} zQhUY)bf;dPqBnmw;De*E)47bLEQ$Q`3I-EjebyDGX*&^wez7f?h)u`1Mrv!;9S`gYVx2)AAXS=VbD*ro^)OwdQSmK(a(BpSYaf3NY4^R&Z?5 z5?m`EO^%G6N{Y$Q9P8JhP75uqb|Zw$EL6#&LqT?k@avadG+&tl1Cnui(1f}`x(GbR z)Dh`5jG)AHuv?Tt*~$*-V2Hu$$Z#B-wf?NrnEw+%)9rZtJ=}@rD>ZiU&})KG{c>GP z8yX)XI1U1f0<6BR!YD|^nzuiMmtH@02xw915im?09|FVF5$bf615m>2AfZOsRYedX z;^_YG)#bj(a3m92f!P@JfwlR9kl5Vm0HE|?y>%m&!F3GBBM;gaUm;N65^6mv0DhR5 z_qU5O62Y7(8?`BXh;5fwkc20}UdVJkzY1&&Md zCs2N8`c@MJNvK~=^W@<>s1a8(I4aKjq)pG`0TV-^d{ z%l~D@ij37lCf^8PYL|g^ktO0=(YqGDOY@gqJEF!WobV?Q-h=oZh_fqWf#py1%2G@i zDtg|mvG8HWmhJ)c>~-9iD>rWt!TQUodTVw<$zgD1%gsZT^$tj|M8adj7E~D%{BmDmY{mp1K>!mk zGUMnkx{%K~N6Whe#+4{AcANY6OZXf&WPEG%U4)^D_A03oUHxWwe9+(PcHfJi(%~cQ ztr~!sA2rrwCn&FX2N(W{`q}+wghIDD0}7?~D>%s(wmiB(xsQuT<0=t%0o#N~CrTAh z`xWTF8iMzt@MqPn+F?MP7AW!cEHv#(XfX2@S0ks!RJDfd=#5#|4G_1Gt#E=fj>XuB z7YQvow*84?hYW=MRcW|qS|ctV>xjT0nBk&26Vmdu-MU*}Le{*OVLB6_IpT9OZT4a! z?-XnBcDl7}alP(Q8}$AQMBUuL^FrZJ^4!F>Hmw75wsA+ttotJ-=typ!)}8Io5M6A~ zI#3$Z61?59ZGQNZMcbrZ4+S6BHV;Ig8&`qpK)-w1lZmQ3A&;2+g771e)!nQxPdI6c zE4Q_NvFoWA(=~$pR^zGCg+$!-8|1w0GBetjOK;G0GyK>-3%Sy~5^|d3wD$Xc?uTom znQabv9}fl?j^1p#n#R~k2 zXlpR=T{}7O@}?e+)9fm*p<_0>ohR;(%(;xLA=&s-mLtNRuXt!gC^p{`M`9*8%AOQe zl!X*^dw`r8V7B<^tc^hH5S;fic}*vz6c9Nx^OdXRXGH2A>0dC68Ur@G9vNWa{ZSXc^v(KG?*L78k74$-4E zf{(l)7% zhFWqSM$|OumyUn$a<|my(}}qe2YhaWzEfBfdKJurZA&R>Bo$K9$yxH3OB`c*YQER$ zk%Km%9O%#ui{we^sW8LCD^}*|+nO%8XpRul9 z2H%YG_6Y%+gB@*$lp>&fVA*1UT9lg|2F-sEv`G;HN>m-es=Q7UNix=MWMzm~Ei|kM zqg4u0naXO2xhT{GWVvufJu zO-TbHfU@Y=fQ$$H(4Q0&f69>>0%pT-$6kf;FVhqfvnNlxj8%IH8GFpz4F1rw?VfDe zV@?Ae9`!%Y?&=T285%_uRrlo!>e|oWb=Vko;)nk(s7EW|n@pgo@lw3#6T9P~Lqmw* zdKa+T?4C0p>6Br_21~J8PsuBKlGN-cysY-g-bSBywuG8DOK(6qvlyHUz*df7vu{C- z&*x#2F+mgOpHgKM%kc1wALp2RBlk6nw4%M zI&Kl%#=1$qj)X|GHfsIc`*Yv$aVe*OFl}EXOwsDz7mj4aJ>}Gl{1Jl#wtf=Kv7izH zZ-t2n7sisN>oq1rQNxLo3itG{xzjF0p82*i2xT1+F3s#XbS9FaaD#n_Z)%c|9XUzr zoD<4f1z#3&15AT?Vd8bT{?GK6V791LCnafVQ>R2}QREgCmN=CFj0&Ob;+nRY-AQkmUnym$3r}J^x$X@Q zi+Nl@ASx;#RH&VMtTH)^xN1XQauyS~&^5`4Y^=v+_~QXrZk>-O@=CQ^k9_U6DIYwn zD{uC-`^%?=*Ms7gEMF#%zdS9GI(KuixcOKQ319Bv1Zc ztlkPe_)`vRJJ-57!Jn^;m|kCPggzJ=`-8@_oCsTFosJxvKZ~F%!9)X)KfwXh1IrP?Q*x{AY!h&K&R3G3q9 zk^G1QhRXCoTI7}g%QL_E-0-uZ0eC2_`rM;8^&8`v54e6)=-2$ue~@2D7+#=XefpFt z&1JhZSv&BaT{Rb7OMn~Wa5l1-O879}@GD@D@?!UQjn;BDP;CM3 zPtE?3-B!UFE(H6OR>_Q>F-;)}H}mwY36>{s*K;9;)eIJ4pE-YhEX``Hix^cCxn_EH z<~jm3*Lz(Bsh1OIJOBG;R1-62!CA=fM4$)O;^L7Kg#YpKc{FM;DEq0)qIWdQez-1tK+8>Oy+E~^zm6ZXzuV=G8Z2=;-=h;%D>d$MVAS>|1Q&Dyz<*X_+h+OJ4GC!+Yg=Om%NZfi~dM5cFOnc`|X$a_|Yj_9igIU%{{Ri>j|s{Za7Wd z&2h@544oG%e*-k(*?G_f6bmCqXirA(~wx+?x z!<=i(mmp%R$mQ|g(-iAdzV14?yb3Wj{7g~zYh&AgwUz%9T%{L7YqXZOsCP>WbA`Z@ z;*6=sz1xI%UXD?)jKe-dGg0dcQ&tSNJ!hL#M2;zRsNH_KYeo|cos9j9{dVGRXE3?R zg-8ba%#(qVt@J?~8c-LRNCfZMQwhY|OF<(LMI#DG+HOw69)(ddH2%4IlDSPyPYwq^ zN4?yc3Ei^CZuvPCq?~yKXZ8qv^U`E)loygAKucAey7bAhN`zd@Z5&}-exnZ%-yAzeB!u4 z9A3ZN*Vg6!7dmR-3&zl`t3BK2IqTjQzxZ4qli*$R8`!N+dOV$$`nBWFRv6}Gx-Q`B zUW2R)F!8@US}i4}**(+Pg%ZWObp>=TiJnRa%x^a9)~khBKgRl$rfvtHf1tssw3`|Q z>|anD+@~G;EZmya>6bw{0DrWl{+1(C()1C>fHU4VZ%Ed6z@(}ap9`O8p5}2tiGk5Ou{}G>lDE1pknAWYn z?}A&5qgr@6M+~t4x;LBa=B%d*42{%)2lC0|xUoURxCAWKZ9hKOvwf=Ey}?G`VjaMu znv2^{ckkPnSxjGXLN$Z;mJf>kp)Liq{7-lDUNCRRa12d4PIM0xQ^`}cuPG+;@8x(Z z!8!@Lrl#A)jM==4z@50R9KotmyaQyqW%X=64D!5gY8Sxk)~KYfQp{+z$)dGL-tu!1 zi}{Ncxt)VnbNCr+vSJ=Q0CGkppRG&=h2G{+@$>r*{_V2=k0oKAe`^GLsCj>F#|daS z;H?M~K`ewgg*sco4Ya}>Z3=KXE8pxGSSk}&mQil{T@TY=Ks{a$m;3#^NQ&gx^l8J2 zM#Ia_fGcjN~J)apaJMw_uo`{Q^YCs^k6nr#7hx zc9?!?ynRFY1Noa0iQ@Z5gK+;XX_r}Hi=vb3Vn6t6H;j{Bs zB0cucy6qRNvF%1bDu8CFYGQaf2JZ|K;qLKkBqE_;9VoR>rZyvH4${xvWPVzYeFNk= zzm^ET)@^cy%hDR+!(KfU>6_)OUome`CV$B`*Na?K$U~P6x6~_wi)a9`3A24)zFC2Jq zZ@Jk!g`5gxF^@L0kvGZ>4QjGfd#RW6%tdV7Ssv{$nqbYq`uav5jic1zwug(&JAK`I zdet?(oGY)UzhH&yu_1_9I1hG%%?+wwW zJGpohd#h50vni2}&5faLu7+n95qM$lUIuamork)Ry+XIAr`>(Vs`3tapVbg6eE*$t z7D4oio;_8)Y|$;X*uEs%*vbDN&w_TMl~2>C@bYSyF!3GZQSn(|T55o@vAPhF6TfQder1Ai-d zAFj#$4QxMCLK%Ma|D)Yg`nju8&T-pPe)s1!^z z((5Z)$y*K_u>7u+ZH&wpJR34`f06uZwUGDX>)mj0d`&698l&}8oeK0v>j6v4F2Rmp zHkr4W64%`t`@Tm3ST_Y^#}&MYT524&gejFL|Ea>@`E1V=M{DsF1H*cby7r6f*RPCt z=nS{*D7FZ;4L;tXM(G}-%5DwiDXftod1;pgJJfyqDKLL+*NMvXzyeyHfMrL*4IsgR zOKEDDl-U50S($Zfqhp&8rn;dl&V(WX=EwL}p|`(i@4d%*Q$?v&DZe1Wn<^xFvh-@; z&Y+M#wf1$b*0VuMBP<7nXeay)+0*;!U>mhGSoG)O`uet<-vxze#Qk^eM2h#_^Y)_} zcgEUf!!{iS(*n-x1vF^E3)+bcGQ2lb@=1~AvvUG6B0OL8NzfT`aujQ;zT@KiL8I*c zS8N;PHL3O-1C1dRjj_9hF-83*LUG-49Uc5Tvpk3$d8}#?m_x8t3C3~UZVWb^k%LOl zE`ZlY+HuJi?E8NOvq95_9UN!`iSjAu+?_pg#s$BC9X&85t^jit_P9Kp$v$Z3U0=Z* zKD=cVCiGmc*0#5;C_143r2PI>Tgon+)y(z+o-sDxNCNT)YiTXf${`>6LwfZlB%@7P zZ<~Z&Je+pdmr1%wFdlP%KinZpoe0F;ZY<%ohQxe({k`|Z1V0b@nngOpr{?vvl<_eG zPTN@gc-vR@t1c<&knhTJ-=JDW{D(bkWj3`a=Wo~kazkv)I9<*%K4r@7+?V`c=#O;jevO0Js^lfiuA6`*2?*BCn zyqs~8fqQ~|s49rI3^CxfR7|_5Y7kZoQxwBQKM$tCPCfpxzHxIR1u-~P8e98Zy-8yD za^u!IMdGJnu&uKe+@%8z89}zKiqIg#7nkrkQK~Gh_JK>KB}<7M9LW+7#DIt%fLun6 zkv!lE&d#w}&j`4pC*WC;tg*!KZE}*l6Kl4dwjXsvL}4SooIdP7JgH2_CD>c=wot)w z>Qa1az>fcTIhRkxIFma0E4H@5610&O#V%z4udrmb_IdL$^ zB!wf({x+oLjzUdi?1_6|y!`>yytemVH((2J)8I}`RvhXY95w-oN&&yaawZjY8&K2n z7byXNRQGI3h=H5CPJ$NhIxF`LS3P)w?YmIUL4ARdtXLrOwj(0%D*O!0KOmNMxm@s? zGbvH(qrw-3It_4&aCIuJuPKuuR1}v2#!C{xih)^+F~tqXAA)dGCanUhIp7Q($@=HD zO%X05j?J6*HdMg8D`bh30ON;TQq2CXQV`6xDv`L2pAEheBWpo3GCI%Gj36c~*(Wy1 zL|_rsY>IK#WypVZopnuJVh?}b@(LD#VJy$7d?BV$20UdXuth(U?o)glHj5HOFX}zq zNUEYVHh>x`9dW9uzF~m3j_Sw9`^^+Og|Js7AkK;6CcuK>D`CmvJ;+T$x_Cytd_tg% zvg|o;g4x4mkt~Y)t2VtXK$xw1tVzO&#`0FcmX`%kar=XIAf z=tGx+7fwkwOVKbZB26?0w+dGfh?|N}oBfHcqB*zzcw&S2dvT<+tOnzbT0R zW(tgBiFNHN`C5B}ZZW=U8;oQ+m2?B-n{toEI!f{s5Y-7!?W$wIzl|+jIx1Uk-w?og zp}H<`7j)$-(n@j;&2^0q!*xY|KF{(wh(C(uZenpo$Mg+$u4_l1I#trEy={A6Oj`W+ zC*?uQ;qEtdgweCyxwJ;|DaVe@p<~m&(2&hnIx*IKUb1L! zNfz^En7msvuxs>(bom8h1CL#c8FiuUS7`MrO$=H0?~iU54tDwJC&7~zyLTO4e2j^j z2&lKbc_P3V--p>(V^TOT7bp@QM!aJVM^9&Gn3B%hWqoFHzd>83ZhNx zRtu31ROEoMC)M0~=`btK(ID~DS9Yz&M2Sv+gy zFfD5%!qYAm8}tr`;-iknNio8o+=5N3*&GCKOLP+PUpGE1@n3ro|FzT+(E1b6lWNQe zMRTwStkd>+2=r}zpu9bDV{70KG=W5P!$_U1Z38Pl!SrlL6%c>1`L-BnA27uPT7OXr ziCs;+ejqT^^4GecGop$i?>GIDWtvTkIahi3wIWS_@YXLg!8jh}wc%fsSJY*Q0lt8f zYou3gwLP9N{?7T5)umt_^L5ehFsRpi`~>R6>li9rx{>)L@*bdf`HZ!zR*_VN4M8(T zK&*o;_$Y?k3qw>MUV;Xaovo%sESp*Pc#b0aR9+M0pEX1(O@D`3CaL_ZZ$S?|=-|9F zgxXf^6IS3&fIA4=sP)3Z6H1(pt31UaX%BKO4iQM{^XqBa;AHAC_o)&OT}@P-f=@cX|(8-xs>#o1Gn zzLtYWP}dujE7PZl?|qU`pqpEH!SgUlz%my)%hBkNK%UzQDzL8*ZJZXFtU#Q~N*J<|b z({4voC#Rd#MG1(R(rAr0tm4sFcQ`*@u2PmYTcQ!Qe~HSu0j0<0Vo`8h18>9R8Hb{H z^J0P^aH=76mN7e~Isz6Zvq;0=N5Dl~*EIPm+ZiMkpd@Mt1gApTpO)8|m1kvKIP2}C z`2YRk$~QeSLiC+_?7ROCF*Cdn6CpAdenYF@4MwwqU!*?opGW!kl+Wh7Fi}e?RN*rB zK(hAfmtF%uC0;go?(m1XBH#ywhF1HSYarqlhcnGK8$%PMfaJqmgHcRt{vO42H%+1U z^o2J`4T}U6906<0H`p=K#l_zW@YqDfXt=2^i2{S6K%a0AB>8&qe>S*$34Rh@?2Vr{ zq<;q%5#3@yK8v^hg4>ztXgS)Vj-9zP#b7MJ7pj_>ld4h{&RvzlWf-Zl`C(vvCmZ)b zEQT*Vv!X%gI^0^s>uyBA@#TRhYwiu{Z{!W;0rgq|#2T1Y2ExrZTodr^+V3n9{0q8t z*ozYsGBXlfErNUGs}qKuVCX1xpa?kOFQA90k^RxjcWTvT>(POU=Z6mRrKi9ME>DyC z=b0JItF{{QYGD;HAh+z(0D%ho2ih#+&!BL-*50p3+rfihP9$)T>LB;%W4z4>DsL9* zQF&vjehIyacK;tq1DO3jx*yjn=#Lgc-qPfp_eQbA_gDzb&A+W2sO1 zTh?r@YGTLPR)VaI7(!HcVvMIJZ|U-=btI_3D@L22p-8&}I1uruz7;V0`I91nmD#3G sk@_bzWL2W6Y{(S$uc_fX6MhIvbPTS3PRow?3I4IzgWsL|>+wtf2l`#6IRF3v literal 0 HcmV?d00001