• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperledger / identus-cloud-agent / 10833924030

12 Sep 2024 03:25PM CUT coverage: 48.864% (+0.07%) from 48.793%
10833924030

Pull #1351

CryptoKnightIOG
ATL-7660: Credential Schema as List

Signed-off-by: Bassam Riman <bassam.riman@iohk.io>
Pull Request #1351: feat: Support Array Of Credential Schema

15 of 28 new or added lines in 3 files covered. (53.57%)

222 existing lines in 68 files now uncovered.

7530 of 15410 relevant lines covered (48.86%)

0.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.97
/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/revocation/BitString.scala
1
package org.hyperledger.identus.pollux.vc.jwt.revocation
2

3
import org.hyperledger.identus.pollux.vc.jwt.revocation.BitStringError.{DecodingError, EncodingError, IndexOutOfBounds}
4
import zio.{IO, UIO, ZIO}
5

6
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
7
import java.util
8
import java.util.zip.{GZIPInputStream, GZIPOutputStream}
9
import java.util.Base64
10

11
class BitString private (val bitSet: util.BitSet, val size: Int) {
12
  def setRevokedInPlace(index: Int, value: Boolean): IO[IndexOutOfBounds, Unit] =
1✔
13
    if (index >= size) ZIO.fail(IndexOutOfBounds(s"bitIndex >= $size: $index"))
1✔
14
    else ZIO.attempt(bitSet.set(index, value)).mapError(t => IndexOutOfBounds(t.getMessage))
1✔
15

16
  def isRevoked(index: Int): IO[IndexOutOfBounds, Boolean] =
1✔
17
    if (index >= size) ZIO.fail(IndexOutOfBounds(s"bitIndex >= $size: $index"))
1✔
18
    else ZIO.attempt(bitSet.get(index)).mapError(t => IndexOutOfBounds(t.getMessage))
1✔
19

20
  def revokedCount(): UIO[Int] = ZIO.succeed(bitSet.stream().count().toInt)
1✔
21

22
  def encoded: IO[EncodingError, String] = {
1✔
23

24
    for {
1✔
25
      bitSetByteArray <- ZIO.succeed(bitSet.toByteArray.map(x => BitString.reverseBits(x).toByte))
1✔
26

27
      /*
28
      This is where the size constructor parameter comes into play (i.e. the initial bitstring size requested by the user).
29
      Interestingly, the underlying 'bitSet.toByteArray()' method only returns the byte array that are 'in use', which means the bytes needed to hold the current bits that are set to true.
30
      E.g. Calling toByteArray on a BitSet of size 64, where all bits are false, will return an empty array. The same BitSet with the fourth bit set to true will return 1 byte. And so on...
31
      So, the paddingByteArray is used to fill the gap between what BitSet returns and what was requested by the user.
32
      If the BitString size is 131.072 and no VC is revoked, the final encoding (as per the spec) should account for all bits, and no only those that are revoked.
33
      The (x + 7) / 8) is used to calculate the number of bytes needed to store a bit array of size x.
34
       */
35
      paddingByteArray = new Array[Byte](((size + 7) / 8) - bitSetByteArray.length)
1✔
36
      baos = new ByteArrayOutputStream()
1✔
37
      _ <- (for {
1✔
38
        gzipOutputStream <- ZIO.attempt(new GZIPOutputStream(baos))
1✔
39
        _ <- ZIO.attempt(gzipOutputStream.write(bitSetByteArray))
1✔
40
        _ <- ZIO.attempt(gzipOutputStream.write(paddingByteArray))
1✔
41
        _ <- ZIO.attempt(gzipOutputStream.close())
1✔
UNCOV
42
      } yield ()).mapError(t => EncodingError(t.getMessage))
×
43
    } yield {
1✔
44
      Base64.getUrlEncoder.encodeToString(baos.toByteArray)
1✔
45
    }
46
  }
47
}
48

49
object BitString {
50
  /*
51
   The minimum size of the bit string according to the VC Status List 2021 specification.
52
   As per the spec "... a minimum revocation bitstring of 131.072, or 16KB uncompressed... is enough to give holders an adequate amount of herd privacy"
53
   Cf. https://www.w3.org/TR/vc-status-list/#revocation-bitstring-length
54
   */
55
  val MIN_SL2021_SIZE: Int = 131072
56

57
  private def reverseBits(b: Int): Int = {
1✔
58
    var result: Int = 0
59
    for (i <- 0 until 8) {
1✔
60
      val bit = (b >> i) & 1
61
      result = (result << 1) | bit
62
    }
63
    result
64
  }
65

66
  def getInstance(): IO[BitStringError, BitString] = getInstance(MIN_SL2021_SIZE)
1✔
67

68
  def getInstance(size: Int): IO[BitStringError, BitString] = {
1✔
69
    if (size % 8 != 0) ZIO.fail(BitStringError.InvalidSize("Bit string size should be a multiple of 8"))
1✔
70
    else ZIO.succeed(BitString(new util.BitSet(size), size))
1✔
71
  }
72

73
  def valueOf(b64Value: String): IO[DecodingError, BitString] = {
1✔
74
    for {
1✔
75
      ba <- ZIO.attempt(Base64.getUrlDecoder.decode(b64Value)).mapError(t => DecodingError(t.getMessage))
1✔
76
    } yield {
77
      val bais = new ByteArrayInputStream(ba)
1✔
78
      val gzipInputStream = new GZIPInputStream(bais)
1✔
79
      val byteArray = gzipInputStream.readAllBytes().map(x => BitString.reverseBits(x).toByte)
1✔
80
      BitString(util.BitSet.valueOf(byteArray), byteArray.length * 8)
1✔
81
    }
82
  }
83
}
84

85
sealed trait BitStringError
86

87
object BitStringError {
88
  final case class InvalidSize(message: String) extends BitStringError
89
  final case class EncodingError(message: String) extends BitStringError
90
  final case class DecodingError(message: String) extends BitStringError
91
  final case class IndexOutOfBounds(message: String) extends BitStringError
92
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc