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

net.glorat.dlcrypto.encode.Proto2Serializer.scala Maven / Gradle / Ivy

The newest version!
package net.glorat.dlcrypto.encode

import java.nio.ByteBuffer
import java.time.LocalDate

import net.glorat.dlcrypto.core.{Address, CryptoSerializer, Hash}
import com.google.protobuf.{ByteString, CodedOutputStream}

/**
  * This class supports a variant of the protobuf proto2 serialization format
  *
  * The purpose is to always have a well defined round-trip serialization format
  * that is safe for the use of cryptographic signing and is also cross-platform
  * and cross-language
  *
  * It restricts proto2 by
  * - Mandating everything is required
  *
  * It does not yet support proto2 features such as
  * - Arrays (except for byte arrays)
  * - Anything remotely complex
  *
  * It extends proto2 by providing custom serializers for
  * - UUID
  * - Hash
  */
object Proto2Serializer extends CryptoSerializer {
  def forSign(form: Product): Array[Byte] = {
    val a = new Array[Byte](lengthOf(form))
    val os = CodedOutputStream.newInstance(a)
    write(form.asInstanceOf[Product], os)
    os.checkNoSpaceLeft()
    a
  }

  def uuidToBytes(uuid:java.util.UUID) : ByteString = {
    val bb: ByteBuffer = ByteBuffer.wrap(new Array[Byte](16))
    bb.putLong(uuid.getMostSignificantBits)
    bb.putLong(uuid.getLeastSignificantBits)
    ByteString.copyFrom(bb.array)
  }

  def lengthOf(p: Product): Int = {
    import com.google.protobuf.CodedOutputStream._

    var ret : Int = 0
    var tag = 0
    p.productIterator.foreach(item => {
      tag += 1
      val s: Int = item match {
        case x: Int => /*if (x!=0)*/  computeInt32Size(tag, x) //else 0
        case x: Hash => computeBytesSize(tag, ByteString.copyFrom(x.getBytes.toArray))
        case x: java.util.UUID => computeBytesSize(tag, uuidToBytes(x))
        case x: LocalDate => computeStringSize(tag, dateToIsoDate(x))
        case x: String => computeStringSize(tag, x)
        case x: Address => computeStringSize(tag, x.value)
        case x: Seq[_] if x.isEmpty => computeBytesSize(tag, ByteString.EMPTY) // Assume Seq[Byte] was type erasured
        case x: Seq[_] if x.head.isInstanceOf[Byte] => computeBytesSize(tag, ByteString.copyFrom(x.asInstanceOf[Seq[Byte]].toArray))
        case _: Seq[_] => throw new IllegalArgumentException("Only Seq[Byte] supported")
        case x: Product => 1 + computeUInt32SizeNoTag(lengthOf(x)) + lengthOf(x)
      }
      ret += s
    })

    ret
  }

  def write(x: Product, os: com.google.protobuf.CodedOutputStream): Unit = {
    var tag : Int = 0
    x.productIterator.foreach(x => {
      tag += 1
      x match {
        case x: Int => /* proto3 if (x!=0)*/ os.writeInt32(tag, x)
        case x: Hash =>
          os.writeByteArray(tag, x.getBytes.toArray)
        case x: java.util.UUID =>
          os.writeBytes(tag, uuidToBytes(x))
        case x: LocalDate =>
          os.writeString(tag, dateToIsoDate(x))
        case x: String =>
          // proto3
          //if (x != "") {
          os.writeString(tag, x)
          //}
        case x:Address =>
          os.writeString(tag, x.value)
        case x: Seq[_] => writeArray(x, tag, os)
        case x: Product =>
          os.writeTag(tag, 2) // Packed array of 1
          os.writeUInt32NoTag(Proto2Serializer.lengthOf(x))
          write(x, os)
      }
    })
  }
  def writeArray(seq: Seq[_], tag: Int, os: com.google.protobuf.CodedOutputStream): Unit = {
    if (seq.nonEmpty) {
      seq.head match {
        case x: Byte =>
          os.writeBytes(tag, ByteString.copyFrom(seq.asInstanceOf[Seq[Byte]].toArray))
        case _ => throw new IllegalArgumentException("Only serializing Seq[Byte]")
      }
    }
    else {
      // Assume Seq[Byte] was type erasured
      os.writeBytes(tag, ByteString.EMPTY)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy