All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.vroste.zio.kinesis.client.ProtobufAggregation.scala Maven / Gradle / Ivy

The newest version!
package nl.vroste.zio.kinesis.client
import com.google.protobuf.UnsafeByteOperations
import nl.vroste.zio.kinesis.client.zionative.protobuf.Messages
import nl.vroste.zio.kinesis.client.zionative.protobuf.Messages.AggregatedRecord
import software.amazon.awssdk.utils.Md5Utils
import zio.Chunk

import java.security.MessageDigest
import scala.util.{ Failure, Try }

object ProtobufAggregation {
  // From https://github.com/awslabs/amazon-kinesis-producer/blob/master/aggregation-format.md
  val magicBytes: Array[Byte] = List(0xf3, 0x89, 0x9a, 0xc2).map(_.toByte).toArray
  val checksumSize            = 16

  @inline
  def putRecordsRequestEntryToRecord(
    data: Chunk[Byte],
    explicitHashKey: Option[String],
    tableIndex: Int
  ): Messages.Record = {
    val b = Messages.Record
      .newBuilder()
      .setData(UnsafeByteOperations.unsafeWrap(data.toArray)) // Safe because chunks are immutable
      .setPartitionKeyIndex(tableIndex.toLong)

    explicitHashKey
      .fold(b)(_ => b.setExplicitHashKeyIndex(tableIndex.toLong))
      .build()
  }

  def encodedSize(ar: AggregatedRecord): Int =
    magicBytes.length + ar.getSerializedSize + checksumSize

  def encodeAggregatedRecord(digest: MessageDigest, ar: AggregatedRecord): Chunk[Byte] = {
    val payload  = ar.toByteArray
    val checksum = Chunk.fromArray(digest.digest(payload))
    Chunk.fromArray(magicBytes) ++ Chunk.fromArray(payload) ++ checksum
  }

  def isAggregatedRecord(data: Chunk[Byte]): Boolean =
    data.slice(0, magicBytes.length).toArray sameElements magicBytes

  def decodeAggregatedRecord(dataChunk: Chunk[Byte]): Try[AggregatedRecord] =
    if (!isAggregatedRecord(dataChunk))
      Failure(new IllegalArgumentException("Data is not an aggregated record"))
    else {
      val payload  = dataChunk.slice(magicBytes.length, dataChunk.size - checksumSize)
      val checksum = dataChunk.slice(dataChunk.size - checksumSize, dataChunk.size)

      // TODO do not instantiate MD5 digest every time
      val calculatedChecksum = Chunk.fromArray(Md5Utils.computeMD5Hash(payload.toArray))

      if (calculatedChecksum != checksum)
        Failure(
          new IllegalArgumentException(
            s"Aggregated record checksum unexpected: ${checksum.mkString("-")} vs ${calculatedChecksum.mkString("-")}"
          )
        )
      else
        Try(Messages.AggregatedRecord.parseFrom(payload.toArray))
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy