Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project overall improvements #3

Merged
merged 8 commits into from
May 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/build
build/
.idea
*.iml
local.properties
56 changes: 47 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,74 @@ KHex is a Kotlin multiplatform library to deal with hexadecimal values.
It was written as part of [KEthereum](https://github.com/komputing/KEthereum) but then extracted as it can be useful
outside this context and deduplicate code this way.

# Download
The following library is available on Jitpack. The current version is:

[![](https://jitpack.io/v/komputing/KHex.svg)](https://jitpack.io/#komputing/KHex)

In order to use it, first of all include the Jitpack maven repository inside your project `build.gradle` file:

```groovy
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
```

Then, include the modules inside your project:

```groovy
dependencies {
implementation 'com.github.komputing.khex:{module}-{platform}:{version}'
}
```

## Available modules
| Module | Description |
| :----- | :---------- |
| `core` | Contains the basic `Hex` object |
| `extensions` | Contains all the useful extension functions |

## Supported platforms
| Module | Supported platforms |
| :------- | :-------: |
| `khex` | `common`, `jvm`, `js`, `native` |
| `khex-extensions` | `common`, `jvm`, `js`, `native` |
| `core` | `common`, `jvm`, `native` |
| `extensions` | `common`, `jvm`, `native` |


## Examples
```groovy
implementation 'com.github.komputing.khex:khex-common:{tag}'
implementation 'com.github.komputing.khex:khex-jvm:{tag}'

implementation 'com.github.komputing.khex:extensions-native:{tag}'
implementation 'com.github.komputing.khex:extensions-js:{tag}'
```

# Usage
## As an Object
You can the `Hex` class contained inside this library asa common Kotlin object in order to encode or decode any
hexadecimal value.
## As a function
You can use the static functions contained inside this library in order to encode or decode any hexadecimal value.

```kotlin
// === Encoding ===

val myByteValue = 31.toByte()
Hex.encode(myByteValue)
encode(myByteValue)

val myByteArrayValue = byteArrayOf(31, 32 , 33)
Hex.encode(myByteArrayValue)
encode(myByteArrayValue)


// === Decoding ===

val myHexString = "0xaa12456789bb"
Hex.decode(myHexString)
decode(myHexString)
```

## As extension functions
By including the `khex-extensions` module, you will be able to access a list of extensions functions that can be useful when
By including the `extensions` module, you will be able to access a list of extensions functions that can be useful when
working with strings and byte arrays/lists.

```kotlin
Expand Down
21 changes: 15 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version"
classpath "org.jlleitschuh.gradle:ktlint-gradle:$ktlint_gradle_version"
}

Expand All @@ -22,16 +23,23 @@ ext.publishLocal = project.hasProperty("publishLocal")

apply from: "gradle/experimental.gradle"

apply plugin: "com.github.ben-manes.versions"

def platforms = ["common", "jvm"]
def platforms = ["common", "jvm", "posix", "darwin"]
def projectNeedsPlatform(project, platform) {
def files = project.projectDir.listFiles()
def hasPosix = files.any { it.name == "posix" }
def hasDarwin = files.any { it.name == "darwin" }

if (hasPosix && hasDarwin) return false

if (hasPosix && platform == "darwin") return false
if (hasDarwin && platform == "posix") return false
if (!hasPosix && !hasDarwin && platform == "darwin") return false

return files.any { it.name == "common" || it.name == platform }
}

allprojects {
group = "org.komputing"
group = "com.github.komputing.khex"
version = configuredVersion
project.ext.hostManager = new HostManager()

Expand All @@ -41,15 +49,16 @@ allprojects {
}

apply plugin: "kotlin-multiplatform"

apply from: rootProject.file("gradle/utility.gradle")

platforms.each { platform ->
if (projectNeedsPlatform(project, platform)) {
configure([it]) {
apply from: rootProject.file("gradle/${platform}.gradle")
}
}
}

apply from: rootProject.file('gradle/dokka.gradle')
apply from: rootProject.file('gradle/publish.gradle')

Expand Down
File renamed without changes.
61 changes: 61 additions & 0 deletions core/common/src/org/komputing/khex/Hex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.komputing.khex


/**
* Represents all the chars used for nibble
*/
private const val CHARS = "0123456789abcdef"

/**
* Encodes the given byte value as an hexadecimal character.
*/
fun encode(value: Byte): String {
return CHARS[value.toInt().shr(4) and 0x0f].toString() + CHARS[value.toInt().and(0x0f)].toString()
}

/**
* Encodes the given byte array value to its hexadecimal representations, and prepends the given prefix to it.
*
* Note that by default the 0x prefix is prepended to the result of the conversion.
* If you want to have the representation without the 0x prefix, pass to this method an empty prefix.
*/
fun encode(value: ByteArray, prefix: String = "0x"): String {
return prefix + value.joinToString("") { encode(it) }
}

/**
* Converts the given ch into its integer representation considering it as an hexadecimal character.
*/
private fun hexToBin(ch: Char): Int = when (ch) {
in '0'..'9' -> ch - '0'
in 'A'..'F' -> ch - 'A' + 10
in 'a'..'f' -> ch - 'a' + 10
else -> -1
}

/**
* Parses the given value reading it as an hexadecimal string, and returns its byte array representation.
*
* Note that either 0x-prefixed string and no-prefixed hex strings are supported.
*
* @throws IllegalArgumentException if the value is not an hexadecimal string.
*/
fun decode(value: String): ByteArray {
// An hex string must always have length multiple of 2
if (value.length % 2 != 0) {
throw IllegalArgumentException("hex-string must have an even number of digits (nibbles)")
}

// Remove the 0x prefix if it is set
val cleanInput = if (value.startsWith("0x")) value.substring(2) else value

return ByteArray(cleanInput.length / 2).apply {
var i = 0
while (i < cleanInput.length) {
this[i / 2] = ((hexToBin(cleanInput[i]) shl 4) + hexToBin(
cleanInput[i + 1]
)).toByte()
i += 2
}
}
}
75 changes: 75 additions & 0 deletions core/common/test/org/komputing/khex/TheHexFun.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.komputing.khex

import kotlin.test.*

class TheHexFun {

private val hexRegex = Regex("0[xX][0-9a-fA-F]+")

@Test
fun weCanProduceSingleDigitHex() {
assertEquals(encode(0.toByte()), "00")
assertEquals(encode(1.toByte()), "01")
assertEquals(encode(15.toByte()), "0f")
}

@Test
fun weCanProduceDoubleDigitHex() {
assertEquals(encode(16.toByte()), "10")
assertEquals(encode(42.toByte()), "2a")
assertEquals(encode(255.toByte()), "ff")
}

@Test
fun prefixIsIgnored() {
assertTrue(decode("0xab").contentEquals(decode("ab")))
}

@Test
fun sizesAreOk() {
assertEquals(decode("0x").size, 0)
assertEquals(decode("ff").size, 1)
assertEquals(decode("ffaa").size, 2)
assertEquals(decode("ffaabb").size, 3)
assertEquals(decode("ffaabb44").size, 4)
assertEquals(decode("0xffaabb4455").size, 5)
assertEquals(decode("0xffaabb445566").size, 6)
assertEquals(decode("ffaabb44556677").size, 7)
}

@Test
fun exceptionOnOddInput() {
assertFailsWith<IllegalArgumentException> {
decode("0xa")
}
}

@Test
fun testRoundTrip() {
assertEquals(encode(decode("00")), "0x00")
assertEquals(encode(decode("ff")), "0xff")
assertEquals(encode(decode("abcdef")), "0xabcdef")
assertEquals(encode(decode("0xaa12456789bb")), "0xaa12456789bb")
}

@Test
fun regexMatchesForHEX() {
assertTrue(hexRegex.matches("0x00"))
assertTrue(hexRegex.matches("0xabcdef123456"))
}

@Test
fun regexFailsForNonHEX() {
assertFalse(hexRegex.matches("q"))
assertFalse(hexRegex.matches(""))
assertFalse(hexRegex.matches("0x+"))
assertFalse(hexRegex.matches("0xgg"))
}

@Test
fun detectsInvalidHex() {
assertFailsWith<IllegalArgumentException> {
decode("0xx")
}
}
}
2 changes: 1 addition & 1 deletion khex-extensions/build.gradle → extensions/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
kotlin.sourceSets {
commonMain.dependencies {
api project(':khex')
api project(':core')
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.komputing.khex.extensions

import org.komputing.khex.Hex
import org.komputing.khex.encode

/**
* Converts [this] [ByteArray] into its hexadecimal string representation prepending to it the given [prefix].
Expand All @@ -10,7 +10,7 @@ import org.komputing.khex.Hex
* pass to this method an empty [prefix].
*/
fun ByteArray.toHexString(prefix: String = "0x"): String {
return Hex.encode(this, prefix)
return encode(this, prefix)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.komputing.khex.extensions

import org.komputing.khex.Hex
import org.komputing.khex.encode

/**
* Converts [this] [List] of bytes into its hexadecimal string representation prepending to it the given [prefix].
Expand All @@ -10,7 +10,7 @@ import org.komputing.khex.Hex
* pass to this method an empty [prefix].
*/
fun List<Byte>.toHexString(prefix: String = "0x"): String {
return Hex.encode(this.toByteArray(), prefix)
return encode(this.toByteArray(), prefix)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.komputing.khex.extensions

import org.komputing.khex.Hex
import org.komputing.khex.decode


/**
* Parses [this] [String] as an hexadecimal value and returns its [ByteArray] representation.
Expand All @@ -10,7 +11,7 @@ import org.komputing.khex.Hex
* @throws IllegalArgumentException if [this] is not an hexadecimal string.
*/
fun String.hexToByteArray(): ByteArray {
return Hex.decode(this)
return decode(this)
}

/**
Expand Down
11 changes: 10 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
kotlin.code.style=official

# config
version=1.0.0-RC1
version=1.0.0-RC2
kotlin.incremental.multiplatform=true

# gradle
Expand All @@ -17,6 +17,15 @@ kotlin_version=1.3.30
ktlint_gradle_version=7.3.0
junit_version=4.12
dokka_version=0.9.18
gradle_node_version=1.2.0
nodejs_version=10.15.1
npm_version=6.4.1

# third-party
org.gradle.parallel=true

# js
mocha_version=4.1.0
mocha_headless_chrome_version=1.8.2
mocha_teamcity_reporter_version=2.4.0
source_map_support_version=0.5.3
4 changes: 3 additions & 1 deletion gradle/common.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ kotlin.sourceSets {
api "org.jetbrains.kotlin:kotlin-test-common:$kotlin_version"
api "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlin_version"
}
}
}

project.ext.set("commonStructure", true)
Loading