content: /*
   * Copyright ConsenSys AG.
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
   * the License. You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
   * specific language governing permissions and limitations under the License.
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  package com.alipay.antchain.bridge.plugins.ethereum2.core.eth;
  
  import java.util.Collection;
  
  import org.apache.tuweni.bytes.Bytes;
  import org.apache.tuweni.bytes.DelegatingBytes;
  import org.apache.tuweni.bytes.MutableBytes;
  import org.hyperledger.besu.ethereum.rlp.RLPException;
  import org.hyperledger.besu.ethereum.rlp.RLPInput;
  import org.web3j.crypto.Hash;
  
  import static com.google.common.base.Preconditions.checkArgument;
  
  /**
   * Bloom filter implementation for storing persistent logs, describes a 2048-bit representation of
   * all log entries of a transaction, except data. Sets the bits of the 2048 byte array, where
   * indices are given by: The lower order 11-bits, of the first three double-bytes, of the KECCAK256,
   * of each value. For instance the address "0x0F572E5295C57F15886F9B263E2F6D2D6C7B5EC6" results in
   * the KECCAK256 hash "bd2b01afcd27800b54d2179edc49e2bffde5078bb6d0b204694169b1643fb108", of which
   * the corresponding double-bytes are: bd2b, 01af, cd27, corresponding to the following bits in the
   * bloom filter: 1323, 431, 1319
   */
  public class LogsBloomFilter extends DelegatingBytes {
  
    /** The constant BYTE_SIZE. */
    public static final int BYTE_SIZE = 256;
  
    private static final int LEAST_SIGNIFICANT_BYTE = 0xFF;
    private static final int LEAST_SIGNIFICANT_THREE_BITS = 0x7;
    private static final int BITS_IN_BYTE = 8;
  
    /** Instantiates a new Logs bloom filter. */
    public LogsBloomFilter() {
      super(Bytes.wrap(new byte[BYTE_SIZE]));
    }
  
    /**
     * Instantiates a new Logs bloom filter.
     *
     * @param data the data
     */
    public LogsBloomFilter(final MutableBytes data) {
      this(data.copy());
    }
  
    /**
     * Instantiates a new Logs bloom filter.
     *
     * @param data the data
     */
    public LogsBloomFilter(final Bytes data) {
      super(data);
      checkArgument(
          data.size() == BYTE_SIZE,
          "Invalid size for bloom filter backing array: expected %s but got %s",
          BYTE_SIZE,
          data.size());
    }
  
    /**
     * Instantiates a new Logs bloom filter.
     *
     * @param logsBloomHexString the logs bloom hex string
     */
    public LogsBloomFilter(final String logsBloomHexString) {
      this(Bytes.fromHexString(logsBloomHexString));
    }
  
    /**
     * Instantiate logs bloom filter From hex string.
     *
     * @param hexString the hex string
     * @return the logs bloom filter
     */
    public static LogsBloomFilter fromHexString(final String hexString) {
      return new LogsBloomFilter(Bytes.fromHexString(hexString));
    }
  
    /**
     * Instantiates an empty logs bloom filter.
     *
     * @return the logs bloom filter
     */
    public static LogsBloomFilter empty() {
      return new LogsBloomFilter(Bytes.wrap(new byte[LogsBloomFilter.BYTE_SIZE]));
    }
  
    /**
     * Creates a bloom filter from the given RLP-encoded input.
     *
     * @param input The input to read from
     * @return the input's corresponding bloom filter
     */
    public static LogsBloomFilter readFrom(final RLPInput input) {
      final Bytes bytes = input.readBytes();
      if (bytes.size() != BYTE_SIZE) {
        throw new RLPException(
            String.format(
                "LogsBloomFilter unexpected size of %s (needs %s)", bytes.size(), BYTE_SIZE));
      }
      return new LogsBloomFilter(bytes);
    }
  
    /**
     * Checks of subset log bloom filter can be contained.
     *
     * @param subset the subset
     * @return the boolean
     */
    public boolean couldContain(final LogsBloomFilter subset) {
      if (subset == null) {
        return true;
      }
      if (subset.size() != size()) {
        return false;
      }
      for (int i = 0; i < size(); i++) {
        final byte subsetValue = subset.get(i);
        if ((get(i) & subsetValue) != subsetValue) {
          return false;
        }
      }
      return true;
    }
  
    /**
     * Instantiate Builder.
     *
     * @return the builder
     */
    public static Builder builder() {
      return new Builder();
    }
  
    /** The Builder. */
    public static final class Builder {
      /** The Data. */
      final MutableBytes data;
  
      private Builder() {
        data = MutableBytes.create(LogsBloomFilter.BYTE_SIZE);
      }
  
      /**
       * Insert filter.
       *
       * @param other the other
       * @return the builder
       */
      public Builder insertFilter(final LogsBloomFilter other) {
        for (int i = 0; i < data.size(); ++i) {
          data.set(i, (byte) ((data.get(i) | other.get(i)) & 0xff));
        }
        return this;
      }
  
      /**
       * Insert log.
       *
       * @param log the log
       * @return the builder
       */
      public Builder insertLog(final EthLog log) {
        insertBytes((Bytes) log.getLogger());
  
        for (final EthLogTopic topic : log.getTopics()) {
          insertBytes(topic);
        }
        return this;
      }
  
      /**
       * Insert logs.
       *
       * @param logs the logs
       * @return the builder
       */
      public Builder insertLogs(final Collection<EthLog> logs) {
        logs.forEach(this::insertLog);
        return this;
      }
  
      /**
       * Insert bytes.
       *
       * @param value the value
       * @return the builder
       */
      public Builder insertBytes(final Bytes value) {
        setBits(Bytes.wrap(Hash.sha3(value.toArray())));
        return this;
      }
  
      /**
       * Build logs bloom filter.
       *
       * @return the logs bloom filter
       */
      public LogsBloomFilter build() {
        return new LogsBloomFilter(data);
      }
  
      /**
       * Discover the low order 11-bits, of the first three double-bytes, of the KECCAK256 hash, of
       * each value and update the bloom filter accordingly.
       *
       * @param hashValue The hash of the log item.
       */
      private void setBits(final Bytes hashValue) {
        for (int counter = 0; counter < 6; counter += 2) {
          final int setBloomBit =
              ((hashValue.get(counter) & LEAST_SIGNIFICANT_THREE_BITS) << BITS_IN_BYTE)
                  + (hashValue.get(counter + 1) & LEAST_SIGNIFICANT_BYTE);
          setBit(setBloomBit);
        }
      }
  
      private void setBit(final int index) {
        final int byteIndex = BYTE_SIZE - 1 - index / 8;
        final int bitIndex = index % 8;
        data.set(byteIndex, (byte) (data.get(byteIndex) | (1 << bitIndex)));
      }
    }
  }
  