|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +package com.swirlds.state.merkle; |
| 3 | + |
| 4 | +import static com.hedera.pbj.runtime.ProtoWriterTools.sizeOfTag; |
| 5 | +import static com.hedera.pbj.runtime.ProtoWriterTools.sizeOfVarInt32; |
| 6 | +import static com.hedera.pbj.runtime.ProtoWriterTools.writeDelimited; |
| 7 | +import static java.lang.StrictMath.toIntExact; |
| 8 | +import static java.util.Objects.requireNonNull; |
| 9 | + |
| 10 | +import com.hedera.pbj.runtime.Codec; |
| 11 | +import com.hedera.pbj.runtime.FieldDefinition; |
| 12 | +import com.hedera.pbj.runtime.FieldType; |
| 13 | +import com.hedera.pbj.runtime.ParseException; |
| 14 | +import com.hedera.pbj.runtime.ProtoConstants; |
| 15 | +import com.hedera.pbj.runtime.ProtoParserTools; |
| 16 | +import com.hedera.pbj.runtime.ProtoWriterTools; |
| 17 | +import com.hedera.pbj.runtime.io.ReadableSequentialData; |
| 18 | +import com.hedera.pbj.runtime.io.WritableSequentialData; |
| 19 | +import com.hedera.pbj.runtime.io.buffer.Bytes; |
| 20 | +import edu.umd.cs.findbugs.annotations.NonNull; |
| 21 | +import java.io.IOException; |
| 22 | + |
| 23 | +/** |
| 24 | + * A record to store state items. |
| 25 | + * |
| 26 | + * <p>This class is very similar to a class with the same name |
| 27 | + * generated from HAPI sources, com.hedera.hapi.platform.state.StateItem. The |
| 28 | + * generated class is not used in the current module to avoid a compile-time |
| 29 | + * dependency on HAPI. |
| 30 | + * |
| 31 | + * <p>At the bytes level, these two classes must be bit to bit identical. It means, |
| 32 | + * bytes for a state value record serialized using {@link StateItem.StateItemCodec} must be |
| 33 | + * identical to bytes created using HAPI StateItem and its codec. See StateValue definition in |
| 34 | + * virtual_map_state.proto for details. |
| 35 | + * |
| 36 | + * @param key key bytes |
| 37 | + * @param value state value object wrapping a domain value object |
| 38 | + */ |
| 39 | +public record StateItem(@NonNull Bytes key, @NonNull Bytes value) { |
| 40 | + public static final Codec<StateItem> CODEC = new StateItemCodec(); |
| 41 | + |
| 42 | + public StateItem { |
| 43 | + requireNonNull(key, "Null key"); |
| 44 | + requireNonNull(value, "Null value"); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * Protobuf Codec for StateItem model object. Generated based on protobuf schema. |
| 49 | + */ |
| 50 | + public static final class StateItemCodec implements Codec<StateItem> { |
| 51 | + |
| 52 | + static final FieldDefinition FIELD_KEY = new FieldDefinition("keyBytes", FieldType.BYTES, false, 2); |
| 53 | + static final FieldDefinition FIELD_VALUE = new FieldDefinition("keyBytes", FieldType.BYTES, false, 3); |
| 54 | + |
| 55 | + /** |
| 56 | + * Parses a StateItem object from ProtoBuf bytes in a {@link ReadableSequentialData}. Throws if in strict mode ONLY. |
| 57 | + * |
| 58 | + * @param input The data input to parse data from, it is assumed to be in a state ready to read with position at start |
| 59 | + * of data to read and limit set at the end of data to read. The data inputs limit will be changed by this |
| 60 | + * method. If there are no bytes remaining in the data input, |
| 61 | + * then the method also returns immediately. |
| 62 | + * @param strictMode This parameter has no effect as {@code StateItem} has bytes only as fields |
| 63 | + * @param parseUnknownFields This parameter has no effect as {@code StateItem} has bytes only as fields |
| 64 | + * @param maxDepth This parameter has no effect as {@code StateItem} has no nested fields |
| 65 | + * @return Parsed StateItem model object |
| 66 | + * @throws ParseException If parsing fails |
| 67 | + */ |
| 68 | + public @NonNull StateItem parse( |
| 69 | + @NonNull final ReadableSequentialData input, |
| 70 | + final boolean strictMode, |
| 71 | + final boolean parseUnknownFields, |
| 72 | + final int maxDepth, |
| 73 | + final int maxSize) |
| 74 | + throws ParseException { |
| 75 | + |
| 76 | + // read key tag |
| 77 | + final int firstFieldNum = extractFieldNum(input); |
| 78 | + Bytes keyBytes = null; |
| 79 | + Bytes valueBytes = null; |
| 80 | + if (firstFieldNum == FIELD_KEY.number()) { |
| 81 | + keyBytes = readBytes(input, FIELD_KEY); |
| 82 | + } else if (firstFieldNum == FIELD_VALUE.number()) { |
| 83 | + valueBytes = readBytes(input, FIELD_VALUE); |
| 84 | + } else { |
| 85 | + throw new ParseException("StateItem unknown field num: " + firstFieldNum); |
| 86 | + } |
| 87 | + |
| 88 | + final int secondFieldNum = extractFieldNum(input); |
| 89 | + if (secondFieldNum == FIELD_KEY.number()) { |
| 90 | + keyBytes = readBytes(input, FIELD_KEY); |
| 91 | + } else if (secondFieldNum == FIELD_VALUE.number()) { |
| 92 | + valueBytes = readBytes(input, FIELD_VALUE); |
| 93 | + } else { |
| 94 | + throw new ParseException("StateItem unknown field num: " + secondFieldNum); |
| 95 | + } |
| 96 | + |
| 97 | + assert keyBytes != null; |
| 98 | + assert valueBytes != null; |
| 99 | + |
| 100 | + return new StateItem(keyBytes, valueBytes); |
| 101 | + } |
| 102 | + |
| 103 | + private static int extractFieldNum(ReadableSequentialData input) throws ParseException { |
| 104 | + final int tag = input.readVarInt(false); |
| 105 | + final int wireType = tag & ProtoConstants.TAG_WIRE_TYPE_MASK; |
| 106 | + if (wireType != ProtoConstants.WIRE_TYPE_DELIMITED.ordinal()) { |
| 107 | + throw new ParseException("StateItem key wire type mismatch: expected=" |
| 108 | + + ProtoConstants.WIRE_TYPE_DELIMITED.ordinal() + ", actual=" + wireType); |
| 109 | + } |
| 110 | + return tag >> ProtoParserTools.TAG_FIELD_OFFSET; |
| 111 | + } |
| 112 | + |
| 113 | + private static Bytes readBytes(ReadableSequentialData input, FieldDefinition fieldDefinition) |
| 114 | + throws ParseException { |
| 115 | + final ProtoConstants wireType = ProtoWriterTools.wireType(fieldDefinition); |
| 116 | + if (wireType != ProtoConstants.WIRE_TYPE_DELIMITED) { |
| 117 | + throw new ParseException("StateItem key wire type mismatch: expected=" |
| 118 | + + ProtoConstants.WIRE_TYPE_DELIMITED.ordinal() + ", actual=" + wireType); |
| 119 | + } |
| 120 | + |
| 121 | + Bytes keyBytes; |
| 122 | + final int keySize = input.readVarInt(false); |
| 123 | + if (keySize == 0) { |
| 124 | + keyBytes = Bytes.EMPTY; |
| 125 | + } else { |
| 126 | + keyBytes = input.readBytes(keySize); |
| 127 | + } |
| 128 | + return keyBytes; |
| 129 | + } |
| 130 | + |
| 131 | + /** |
| 132 | + * Write out a StateItem model to output stream in protobuf format. |
| 133 | + * |
| 134 | + * @param data The input model data to write |
| 135 | + * @param out The output stream to write to |
| 136 | + * @throws IOException If there is a problem writing |
| 137 | + */ |
| 138 | + public void write(@NonNull StateItem data, @NonNull final WritableSequentialData out) throws IOException { |
| 139 | + writeDelimited(out, FIELD_KEY, toIntExact(data.key.length()), v -> v.writeBytes(data.key)); |
| 140 | + writeDelimited(out, FIELD_VALUE, toIntExact(data.value.length()), v -> v.writeBytes(data.value)); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * {@inheritDoc} |
| 145 | + */ |
| 146 | + public int measure(@NonNull final ReadableSequentialData input) throws ParseException { |
| 147 | + final var start = input.position(); |
| 148 | + parse(input); |
| 149 | + final var end = input.position(); |
| 150 | + return (int) (end - start); |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * {@inheritDoc} |
| 155 | + */ |
| 156 | + @Override |
| 157 | + public int measureRecord(StateItem item) { |
| 158 | + int size = 0; |
| 159 | + |
| 160 | + size += sizeOfTag(FIELD_KEY); |
| 161 | + // key size counter size |
| 162 | + size += sizeOfVarInt32(toIntExact(item.key.length())); |
| 163 | + // Key size |
| 164 | + size += toIntExact(item.key.length()); |
| 165 | + |
| 166 | + size += sizeOfTag(FIELD_VALUE); |
| 167 | + // value size counter size |
| 168 | + size += sizeOfVarInt32(toIntExact(item.value().length())); |
| 169 | + // value size |
| 170 | + size += toIntExact(item.value.length()); |
| 171 | + |
| 172 | + return size; |
| 173 | + } |
| 174 | + |
| 175 | + /** |
| 176 | + * {@inheritDoc} |
| 177 | + */ |
| 178 | + @Override |
| 179 | + public boolean fastEquals(@NonNull StateItem item, @NonNull ReadableSequentialData input) |
| 180 | + throws ParseException { |
| 181 | + return item.equals(parse(input)); |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * {@inheritDoc} |
| 186 | + */ |
| 187 | + @Override |
| 188 | + public StateItem getDefaultInstance() { |
| 189 | + return new StateItem(Bytes.EMPTY, Bytes.EMPTY); |
| 190 | + } |
| 191 | + } |
| 192 | +} |
0 commit comments