Skip to content

Commit

Permalink
More ASN.1
Browse files Browse the repository at this point in the history
  • Loading branch information
JesusMcCloud committed Oct 16, 2024
1 parent 54e4973 commit 343b62b
Show file tree
Hide file tree
Showing 29 changed files with 242 additions and 255 deletions.
4 changes: 0 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ tasks.getByName("dokkaHtmlMultiModule") {
allprojects {
apply(plugin = "org.jetbrains.dokka")
group = rootProject.group

repositories {
mavenLocal()
}
}

tasks.register<Copy>("copyChangelog") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Asn1BitString private constructor(

@Throws(Asn1Exception::class)
override fun doDecode(src: Asn1Primitive): Asn1BitString {
if (src.length == 0) return Asn1BitString(0, byteArrayOf())
if (src.length == 0L) return Asn1BitString(0, byteArrayOf())
if (src.content.first() > 7) throw Asn1Exception("Number of padding bits < 7")
return Asn1BitString(src.content[0], src.content.sliceArray(1..<src.content.size))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import at.asitplus.catching
import at.asitplus.signum.indispensable.asn1.Asn1Element.Tag.Template.Companion.withClass
import at.asitplus.signum.indispensable.asn1.encoding.*
import at.asitplus.signum.indispensable.io.ByteArrayBase64Serializer
import at.asitplus.signum.indispensable.io.first
import at.asitplus.signum.indispensable.io.ByteIteratorSource
import io.matthewnelson.encoding.base16.Base16
import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray
import kotlinx.io.*
import kotlinx.io.bytestring.ByteString
import kotlinx.io.bytestring.toHexString
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
Expand All @@ -36,7 +34,7 @@ sealed class Asn1Element(
if (this is Asn1Structure && other !is Asn1Structure) return false
if (this is Asn1Primitive && other !is Asn1Primitive) return false
return if (this is Asn1Primitive) {
(content == other.content)
(content.contentEquals(other.content))
} else {
this as Asn1Structure
other as Asn1Structure
Expand All @@ -63,12 +61,12 @@ sealed class Asn1Element(
val encodedLength by lazy { Buffer().apply { encodeLength(length) }.snapshot() }

/**
* Length (as a plain `Int` to work with it in code) of the contained data.
* Length (as a plain `Long` to work with it in code) of the contained data.
* For a primitive, this is just the size of the held bytes.
* For a structure, it is the sum of the number of bytes needed to encode all held child nodes.
*/
val length: Long by lazy {
children?.fold(0L) { acc, extendedTlv -> acc + extendedTlv.overallLength } ?: tlv.contentLength.toLong()
children?.fold(0L) { acc, extendedTlv -> acc + extendedTlv.overallLength } ?: tlv.contentLength
}

/**
Expand All @@ -80,12 +78,12 @@ sealed class Asn1Element(

val tag by lazy { tlv.tag }

val derEncoded: ByteString by lazy {
val derEncoded: ByteArray by lazy {
(children?.fold(Buffer()) { acc, extendedTlv -> acc.apply { write(extendedTlv.derEncoded) } }
?.let {
Buffer().apply { write(tlv.tag.encodedTag); encodeLength(it.size); it.transferTo(this) }
}?.snapshot()
?: Buffer().apply { write(tlv.tag.encodedTag); write(encodedLength);write(tlv.content) }.snapshot())
}?.readByteArray()
?: Buffer().apply { write(tlv.tag.encodedTag); write(encodedLength);write(tlv.content) }.readByteArray())
}

override fun toString(): String = "(tag=${tlv.tag}" +
Expand Down Expand Up @@ -236,13 +234,16 @@ sealed class Asn1Element(
@Serializable
@ConsistentCopyVisibility
data class Tag private constructor(
val tagValue: ULong, val encodedTagLength: Long,
@Serializable(with = ByteArrayBase64Serializer::class) val encodedTag: ByteString
val tagValue: ULong, val encodedTagLength: Int,
@Serializable(with = ByteArrayBase64Serializer::class) val encodedTag: ByteArray
) : Comparable<Tag> {
private constructor(values: Triple<ULong, Long, ByteString>) : this(values.first, values.second, values.third)
constructor(derEncoded: Buffer) : this(
derEncoded.decodeTagShallow()
.let { Triple(it.first, it.second.size, derEncoded.snapshot()) }
private constructor(values: Triple<ULong, Int, ByteArray>) : this(values.first, values.second, values.third)

//TODO this CTOR is internally called only using already validated inputs.
// We need another CTOR to prevent double-parsing and byte copying
constructor(derEncoded: ByteArray) : this(
ByteIteratorSource(derEncoded.iterator()).buffered().decodeTag()
.let { Triple(it.first, it.second.size, derEncoded) }
)

/**
Expand All @@ -259,7 +260,7 @@ sealed class Asn1Element(
)

companion object {
private fun encode(tagClass: TagClass, constructed: Boolean, tagValue: ULong): Buffer {
private fun encode(tagClass: TagClass, constructed: Boolean, tagValue: ULong): ByteArray {
val derEncoded: Buffer =
Buffer().apply {
if (tagValue <= 30u) {
Expand All @@ -269,7 +270,7 @@ sealed class Asn1Element(
writeAsn1VarInt(tagValue)
}
}
return derEncoded
return derEncoded.readByteArray()
}

private fun UByte.withTagProperties(constructed: Boolean, tagClass: TagClass): UByte =
Expand Down Expand Up @@ -341,12 +342,12 @@ sealed class Asn1Element(
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Tag) return false
if (encodedTag != other.encodedTag) return false
if (!encodedTag.contentEquals(other.encodedTag)) return false

return true
}

override fun hashCode(): Int = encodedTag.hashCode()
override fun hashCode(): Int = encodedTag.contentHashCode()

/**
* creates a new Tag from this object, overriding the class. Useful for implicitTagging (see [Asn1Structure.withImplicitTag])
Expand Down Expand Up @@ -448,7 +449,7 @@ sealed class Asn1Structure(
*/
val isSorted: Boolean = false
) :
Asn1Element(TLV.Immutable(tag, ByteString()), if (!isSorted) children else children.sortedBy { it.tag }) {
Asn1Element(TLV.Immutable(tag, byteArrayOf()), if (!isSorted) children else children.sortedBy { it.tag }) {

public override val children: List<Asn1Element>
get() = super.children!!
Expand Down Expand Up @@ -579,8 +580,8 @@ class Asn1CustomStructure private constructor(
) : this(children, tag.toULong(), tagClass, sortChildren)


override val content: ByteString by lazy {
if (!tag.isConstructed) Buffer().apply { children.forEach { write(it.derEncoded) } }.snapshot()
override val content: ByteArray by lazy {
if (!tag.isConstructed) Buffer().apply { children.forEach { write(it.derEncoded) } }.readByteArray()
else super.content
}

Expand Down Expand Up @@ -616,8 +617,8 @@ class Asn1CustomStructure private constructor(
class Asn1EncapsulatingOctetString(children: List<Asn1Element>) :
Asn1Structure(Tag.OCTET_STRING, children),
Asn1OctetString<Asn1EncapsulatingOctetString> {
override val content: ByteString by lazy {
Buffer().apply { children.forEach { write(it.derEncoded) } }.snapshot()
override val content: ByteArray by lazy {
Buffer().apply { children.forEach { write(it.derEncoded) } }.readByteArray()
}

override fun unwrap() = this
Expand All @@ -633,10 +634,10 @@ class Asn1EncapsulatingOctetString(children: List<Asn1Element>) :
* ASN.1 OCTET STRING 0x04 ([BERTags.OCTET_STRING]) containing data, which does not decode to an [Asn1Element]
* @param content the data to hold
*/
class Asn1PrimitiveOctetString(content: ByteString) : Asn1Primitive(Tag.OCTET_STRING, content),
class Asn1PrimitiveOctetString(content: ByteArray) : Asn1Primitive(Tag.OCTET_STRING, content),
Asn1OctetString<Asn1PrimitiveOctetString> {

override val content: ByteString get() = super.content
override val content: ByteArray get() = super.content

override fun unwrap() = this

Expand Down Expand Up @@ -689,23 +690,23 @@ class Asn1SetOf @Throws(Asn1Exception::class) internal constructor(children: Lis
/**
* ASN.1 primitive. Hold o children, but [content] under [tag]
*/
open class Asn1Primitive(tag: Tag, content: ByteString) : Asn1Element(TLV.Immutable(tag, content), null) {
open class Asn1Primitive(tag: Tag, content: ByteArray) : Asn1Element(TLV.Immutable(tag, content), null) {
init {
if (tag.isConstructed) throw IllegalArgumentException("A primitive cannot have a CONSTRUCTED tag")
}

override fun toString() = "Primitive" + super.toString()

constructor(tagValue: ULong, content: ByteString) : this(Tag(tagValue, false), content)
constructor(tagValue: ULong, content: ByteArray) : this(Tag(tagValue, false), content)

constructor(tagValue: UByte, content: ByteString) : this(tagValue.toULong(), content)
constructor(tagValue: UByte, content: ByteArray) : this(tagValue.toULong(), content)

override fun prettyPrint(indent: Int) = (" " * indent) + "Primitive" + super.prettyPrint(indent)

/**
* Raw data contained in this ASN.1 primitive in its encoded form. Requires decoding to interpret it
*/
public override val content: ByteString
public override val content: ByteArray
get() = super.content
}

Expand All @@ -727,7 +728,7 @@ interface Asn1OctetString<T : Asn1Element> {
*
* It makes sense to have this for both kinds of octet strings, since many intermediate processing steps don't care about semantics.
*/
val content: ByteString
val content: ByteArray

/**
* Returns the actual type of this object inside the [Asn1Element] class hierarchy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ sealed class Asn1String : Asn1Encodable<Asn1Primitive> {
override val tag = BERTags.NUMERIC_STRING.toULong()
}

override fun encodeToTlv() = Asn1Primitive(tag, ByteString(value.encodeToByteArray()))
override fun encodeToTlv() = Asn1Primitive(tag, value.encodeToByteArray())
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package at.asitplus.signum.indispensable.asn1
import kotlinx.io.Buffer
import kotlinx.io.bytestring.ByteString
import kotlinx.io.bytestring.toHexString
import kotlinx.io.readByteArray
import kotlinx.io.snapshot

internal sealed class TLV<T>(val tag: Asn1Element.Tag, val content: T) {
Expand All @@ -24,16 +25,17 @@ internal sealed class TLV<T>(val tag: Asn1Element.Tag, val content: T) {
if (this::class != other::class) return false

other as Immutable
this as Immutable

if (tag != other.tag) return false
if (content != other.content) return false
if (!content.contentEquals(other.content)) return false

return true
}

override fun hashCode(): Int {
var result = tag.hashCode()
result = 31 * result + content.hashCode()
result = 31 * result + if(this is Shallow) content.hashCode() else (content as ByteArray).contentHashCode()
return result
}

Expand Down Expand Up @@ -67,14 +69,14 @@ internal sealed class TLV<T>(val tag: Asn1Element.Tag, val content: T) {
/**
* Deep-copies this shallow TLV into an [Immutable] one. Does not consume anything from [content]
*/
fun deepCopy() = Immutable(tag, content.snapshot())
fun deepCopy() = Immutable(tag, content.copy().readByteArray())

}

/**
* Immutable TLV, containing a deep copy of the parsed bytes
*/
class Immutable(tag: Asn1Element.Tag, content: ByteString) : TLV<ByteString>(tag, content) {
class Immutable(tag: Asn1Element.Tag, content: ByteArray) : TLV<ByteArray>(tag, content) {

override val contentLength: Long by lazy { content.size.toLong() }

Expand Down
Loading

0 comments on commit 343b62b

Please sign in to comment.