diff --git a/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapResourceStub.kt b/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapResourceStub.kt index bf183ad..76710b9 100644 --- a/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapResourceStub.kt +++ b/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapResourceStub.kt @@ -9,6 +9,7 @@ import org.eclipse.californium.core.server.resources.CoapExchange class CoapResourceStub : CoapResource("coap-path") { var lastRequestPayload = byteArrayOf() + @Suppress("FUNCTION_NAME_INCORRECT_CASE") // Prevent diktat from renaming to lower camel case override fun handlePOST(coapExchange: CoapExchange) { lastRequestPayload = coapExchange.requestPayload } diff --git a/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapServerHelpers.kt b/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapServerHelpers.kt index 30c4ac1..7aaca08 100644 --- a/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapServerHelpers.kt +++ b/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/CoapServerHelpers.kt @@ -8,7 +8,10 @@ import org.eclipse.californium.core.network.Endpoint import org.eclipse.californium.elements.config.Configuration object CoapServerHelpers { - fun createEndpoint(config: Configuration, port: Int): Endpoint { - return CoapEndpoint.builder().setConfiguration(config).setPort(port).build() - } + fun createEndpoint( + config: Configuration, + port: Int): Endpoint = CoapEndpoint.builder() + .setConfiguration(config) + .setPort(port) + .build() } diff --git a/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/SimulatorIntegrationTest.kt b/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/SimulatorIntegrationTest.kt index 24e75cf..80773fb 100644 --- a/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/SimulatorIntegrationTest.kt +++ b/application/src/integrationTest/kotlin/org/gxf/crestdevicesimulator/SimulatorIntegrationTest.kt @@ -5,8 +5,6 @@ package org.gxf.crestdevicesimulator import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper -import java.net.URI -import java.time.Duration import org.assertj.core.api.Assertions.assertThat import org.awaitility.Awaitility import org.eclipse.californium.core.CoapServer @@ -17,22 +15,23 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.boot.test.context.SpringBootTest import org.springframework.core.io.ClassPathResource +import java.net.URI +import java.time.Duration + @SpringBootTest class SimulatorIntegrationTest { - - @Value("\${simulator.config.uri}") private lateinit var uri: URI - private val mapper = ObjectMapper() - private lateinit var coapServer: CoapServer private val coapResourceStub = CoapResourceStub() - private val expectedJsonNode = - mapper.readTree(ClassPathResource("messages/kod-message.json").file) + private val expectedJsonNode = mapper.readTree(ClassPathResource("messages/kod-message.json").file) + + @Value("\${simulator.config.uri}") + private lateinit var uri: URI + private lateinit var coapServer: CoapServer @BeforeEach fun setup() { coapServer = CoapServer(Configuration.getStandard()) - coapServer.addEndpoint( - CoapServerHelpers.createEndpoint(Configuration.getStandard(), uri.port)) + coapServer.addEndpoint(CoapServerHelpers.createEndpoint(Configuration.getStandard(), uri.port)) coapServer.add(coapResourceStub) coapServer.start() } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/Application.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/SimulatorApplication.kt similarity index 100% rename from application/src/main/kotlin/org/gxf/crestdevicesimulator/Application.kt rename to application/src/main/kotlin/org/gxf/crestdevicesimulator/SimulatorApplication.kt diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/AdvancedSingleIdentityPskStore.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/AdvancedSingleIdentityPskStore.kt index 180fc76..8217a93 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/AdvancedSingleIdentityPskStore.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/AdvancedSingleIdentityPskStore.kt @@ -3,8 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.configuration -import java.net.InetSocketAddress -import javax.crypto.SecretKey import org.eclipse.californium.scandium.dtls.ConnectionId import org.eclipse.californium.scandium.dtls.HandshakeResultHandler import org.eclipse.californium.scandium.dtls.PskPublicInformation @@ -14,16 +12,18 @@ import org.eclipse.californium.scandium.util.SecretUtil import org.eclipse.californium.scandium.util.ServerNames import org.springframework.beans.factory.annotation.Value -class AdvancedSingleIdentityPskStore(private val identity: String) : AdvancedPskStore { - - companion object { - private const val ALGORITHM = "PSK" - } - - @Value("\${simulator.config.psk-key}") lateinit var defaultKey: String +import java.net.InetSocketAddress +import javax.crypto.SecretKey +/** + * @param identity + */ +class AdvancedSingleIdentityPskStore(private val identity: String) : AdvancedPskStore { var key: String = "" + @Value("\${simulator.config.psk-key}") + lateinit var defaultKey: String + override fun hasEcdhePskSupported() = true override fun requestPskSecretResult( @@ -36,8 +36,7 @@ class AdvancedSingleIdentityPskStore(private val identity: String) : AdvancedPsk useExtendedMasterSecret: Boolean ): PskSecretResult { if (key.isEmpty()) { - return PskSecretResult( - cid, identity, SecretUtil.create(defaultKey.toByteArray(), ALGORITHM)) + return PskSecretResult(cid, identity, SecretUtil.create(defaultKey.toByteArray(), ALGORITHM)) } return PskSecretResult(cid, identity, SecretUtil.create(key.toByteArray(), ALGORITHM)) } @@ -48,4 +47,8 @@ class AdvancedSingleIdentityPskStore(private val identity: String) : AdvancedPsk override fun setResultHandler(resultHandler: HandshakeResultHandler?) { // No async handler is used, so no implementation needed } + + companion object { + private const val ALGORITHM = "PSK" + } } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CaliforniumConfiguration.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CaliforniumConfiguration.kt index 8db4572..2416eea 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CaliforniumConfiguration.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CaliforniumConfiguration.kt @@ -11,9 +11,11 @@ import org.eclipse.californium.scandium.config.DtlsConfig import org.eclipse.californium.scandium.config.DtlsConfig.DtlsRole import org.springframework.context.annotation.Bean +/** + * @param simulatorProperties + */ @org.springframework.context.annotation.Configuration class CaliforniumConfiguration(private val simulatorProperties: SimulatorProperties) { - init { DtlsConfig.register() CoapConfig.register() @@ -22,12 +24,10 @@ class CaliforniumConfiguration(private val simulatorProperties: SimulatorPropert } @Bean - fun configure(): Configuration { - return Configuration.getStandard() - .set(CoapConfig.COAP_PORT, simulatorProperties.uri.port) - .set(CoapConfig.COAP_SECURE_PORT, simulatorProperties.uri.port) - .set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY) - .set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, false) - .set(DtlsConfig.DTLS_CIPHER_SUITES, simulatorProperties.cipherSuites) - } + fun configure(): Configuration = Configuration.getStandard() + .set(CoapConfig.COAP_PORT, simulatorProperties.uri.port) + .set(CoapConfig.COAP_SECURE_PORT, simulatorProperties.uri.port) + .set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY) + .set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, false) + .set(DtlsConfig.DTLS_CIPHER_SUITES, simulatorProperties.cipherSuites) } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CoapClientConfiguration.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CoapClientConfiguration.kt index 4c33a9d..3f60055 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CoapClientConfiguration.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/CoapClientConfiguration.kt @@ -8,12 +8,15 @@ import org.gxf.crestdevicesimulator.simulator.data.entity.PreSharedKeyStatus import org.gxf.crestdevicesimulator.simulator.data.repository.PskRepository import org.springframework.context.annotation.Bean +/** + * @param simulatorProperties + * @param pskRepository + */ @org.springframework.context.annotation.Configuration class CoapClientConfiguration( private val simulatorProperties: SimulatorProperties, private val pskRepository: PskRepository ) { - @Bean fun pskStore(): AdvancedSingleIdentityPskStore { val store = AdvancedSingleIdentityPskStore(simulatorProperties.pskIdentity) @@ -21,7 +24,9 @@ class CoapClientConfiguration( pskRepository.findFirstByIdentityAndStatusOrderByRevisionDesc( simulatorProperties.pskIdentity, PreSharedKeyStatus.ACTIVE) - if (savedKey == null) { + savedKey?.let { + store.key = savedKey.preSharedKey + } ?: run { val initialPreSharedKey = PreSharedKey( simulatorProperties.pskIdentity, @@ -31,8 +36,6 @@ class CoapClientConfiguration( PreSharedKeyStatus.ACTIVE) pskRepository.save(initialPreSharedKey) store.key = simulatorProperties.pskKey - } else { - store.key = savedKey.preSharedKey } return store diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/SimulatorProperties.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/SimulatorProperties.kt index 5e06f1b..cb097f0 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/SimulatorProperties.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/configuration/SimulatorProperties.kt @@ -3,12 +3,26 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.configuration -import java.net.URI -import java.time.Duration import org.eclipse.californium.scandium.dtls.cipher.CipherSuite import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.core.io.Resource +import java.net.URI +import java.time.Duration + +/** + * @property uri + * @property pskIdentity + * @property pskKey + * @property pskSecret + * @property sleepDuration + * @property scheduledMessage + * @property successMessage + * @property failureMessage + * @property rebootSuccessMessage + * @property produceValidCbor + * @property cipherSuites + */ @ConfigurationProperties(prefix = "simulator.config") class SimulatorProperties( val uri: URI, diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/observability/ObservabilityConfiguration.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/observability/ObservabilityConfiguration.kt index f6bae2d..7a8b4dd 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/observability/ObservabilityConfiguration.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/observability/ObservabilityConfiguration.kt @@ -10,9 +10,6 @@ import org.springframework.context.annotation.Configuration @Configuration(proxyBeanMethods = false) internal class ObservabilityConfiguration { - @Bean - fun observedAspect(observationRegistry: ObservationRegistry): ObservedAspect { - return ObservedAspect(observationRegistry) - } + fun observedAspect(observationRegistry: ObservationRegistry) = ObservedAspect(observationRegistry) } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/Simulator.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/Simulator.kt index 50fc5be..9f765e2 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/Simulator.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/Simulator.kt @@ -3,22 +3,27 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator +import org.gxf.crestdevicesimulator.configuration.SimulatorProperties +import org.gxf.crestdevicesimulator.simulator.message.MessageHandler + import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import io.github.oshai.kotlinlogging.KotlinLogging -import org.gxf.crestdevicesimulator.configuration.SimulatorProperties -import org.gxf.crestdevicesimulator.simulator.message.MessageHandler import org.springframework.boot.CommandLineRunner import org.springframework.core.io.Resource import org.springframework.stereotype.Component +/** + * @param simulatorProperties + * @param messageHandler + * @param mapper + */ @Component class Simulator( private val simulatorProperties: SimulatorProperties, private val messageHandler: MessageHandler, private val mapper: ObjectMapper ) : CommandLineRunner { - private val logger = KotlinLogging.logger {} override fun run(args: Array) { diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/coap/CoapClientService.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/coap/CoapClientService.kt index a434562..54234f1 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/coap/CoapClientService.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/coap/CoapClientService.kt @@ -3,7 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.coap -import java.net.InetSocketAddress +import org.gxf.crestdevicesimulator.configuration.AdvancedSingleIdentityPskStore +import org.gxf.crestdevicesimulator.configuration.SimulatorProperties + import org.eclipse.californium.core.CoapClient import org.eclipse.californium.core.coap.CoAP import org.eclipse.californium.core.network.CoapEndpoint @@ -12,17 +14,21 @@ import org.eclipse.californium.scandium.DTLSConnector import org.eclipse.californium.scandium.MdcConnectionListener import org.eclipse.californium.scandium.config.DtlsConnectorConfig import org.eclipse.californium.scandium.dtls.ProtocolVersion -import org.gxf.crestdevicesimulator.configuration.AdvancedSingleIdentityPskStore -import org.gxf.crestdevicesimulator.configuration.SimulatorProperties import org.springframework.stereotype.Service +import java.net.InetSocketAddress + +/** + * @param simulatorProperties + * @param advancedSingleIdentityPskStore + * @param configuration + */ @Service class CoapClientService( private val simulatorProperties: SimulatorProperties, private val advancedSingleIdentityPskStore: AdvancedSingleIdentityPskStore, private val configuration: Configuration ) { - fun shutdownCoapClient(coapClient: CoapClient) { coapClient.endpoint.stop() coapClient.endpoint.destroy() diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKey.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKey.kt index 440bc0a..9c77753 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKey.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKey.kt @@ -3,13 +3,21 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.data.entity +import org.gxf.crestdevicesimulator.simulator.data.repository.PreSharedKeyCompositeKey + import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated import jakarta.persistence.Id import jakarta.persistence.IdClass -import org.gxf.crestdevicesimulator.simulator.data.repository.PreSharedKeyCompositeKey +/** + * @property identity + * @property revision + * @property preSharedKey + * @property secret + * @property status + */ @Entity @IdClass(PreSharedKeyCompositeKey::class) class PreSharedKey( diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKeyStatus.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKeyStatus.kt index 53effef..02c3fdd 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKeyStatus.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/entity/PreSharedKeyStatus.kt @@ -6,6 +6,7 @@ package org.gxf.crestdevicesimulator.simulator.data.entity enum class PreSharedKeyStatus { ACTIVE, INACTIVE, + INVALID, PENDING, - INVALID + ; } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/repository/PreSharedKeyCompositeKey.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/repository/PreSharedKeyCompositeKey.kt index 731d095..b383a7e 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/repository/PreSharedKeyCompositeKey.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/data/repository/PreSharedKeyCompositeKey.kt @@ -5,6 +5,10 @@ package org.gxf.crestdevicesimulator.simulator.data.repository import java.io.Serializable +/** + * @property identity + * @property revision + */ class PreSharedKeyCompositeKey(val identity: String?, val revision: Int?) : Serializable { constructor() : this(null, null) } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandler.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandler.kt index 1ab0a57..401c87b 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandler.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandler.kt @@ -3,6 +3,13 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.message +import org.gxf.crestdevicesimulator.configuration.SimulatorProperties +import org.gxf.crestdevicesimulator.simulator.CborFactory +import org.gxf.crestdevicesimulator.simulator.coap.CoapClientService +import org.gxf.crestdevicesimulator.simulator.response.CommandService +import org.gxf.crestdevicesimulator.simulator.response.PskExtractor +import org.gxf.crestdevicesimulator.simulator.response.command.PskService + import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.node.ArrayNode @@ -14,14 +21,15 @@ import org.eclipse.californium.core.CoapClient import org.eclipse.californium.core.CoapResponse import org.eclipse.californium.core.coap.MediaTypeRegistry import org.eclipse.californium.core.coap.Request -import org.gxf.crestdevicesimulator.configuration.SimulatorProperties -import org.gxf.crestdevicesimulator.simulator.CborFactory -import org.gxf.crestdevicesimulator.simulator.coap.CoapClientService -import org.gxf.crestdevicesimulator.simulator.response.CommandService -import org.gxf.crestdevicesimulator.simulator.response.PskExtractor -import org.gxf.crestdevicesimulator.simulator.response.command.PskService import org.springframework.stereotype.Service +/** + * @param coapClientService + * @param simulatorProperties + * @param pskService + * @param mapper + * @param commandService + */ @Service class MessageHandler( private val coapClientService: CoapClientService, @@ -32,13 +40,6 @@ class MessageHandler( ) { private val logger = KotlinLogging.logger {} - companion object { - private const val URC_FIELD = "URC" - private const val URC_PSK_SUCCESS = "PSK:SET" - private const val URC_PSK_ERROR = "PSK:EQER" - private const val DL_FIELD = "DL" - } - fun sendMessage(jsonNode: JsonNode) { val request = createRequest(jsonNode) logger.info { "Sending request: $request" } @@ -55,14 +56,19 @@ class MessageHandler( } catch (e: Exception) { e.printStackTrace() } finally { - if (coapClient != null) coapClientService.shutdownCoapClient(coapClient) + coapClient?.let { + coapClientService.shutdownCoapClient(coapClient) + } } } fun createRequest(jsonNode: JsonNode): Request { val payload = - if (simulatorProperties.produceValidCbor) CborFactory.createValidCbor(jsonNode) - else CborFactory.createInvalidCbor() + if (simulatorProperties.produceValidCbor) { + CborFactory.createValidCbor(jsonNode) + } else { + CborFactory.createInvalidCbor() + } return Request.newPost() .apply { options.setContentFormat(MediaTypeRegistry.APPLICATION_CBOR) } @@ -73,15 +79,9 @@ class MessageHandler( if (response.isSuccess) { val payload = String(response.payload) when { - PskExtractor.hasPskSetCommand(payload) -> { - handlePskSetCommand(payload) - } - pskService.isPendingKeyPresent() -> { - pskService.changeActiveKey() - } - commandService.hasRebootCommand(payload) -> { - sendRebootSuccesMessage(payload) - } + PskExtractor.hasPskSetCommand(payload) -> handlePskSetCommand(payload) + pskService.isPendingKeyPresent() -> pskService.changeActiveKey() + commandService.hasRebootCommand(payload) -> sendRebootSuccesMessage(payload) } } else { logger.error { "Received error response with ${response.code}" } @@ -136,9 +136,16 @@ class MessageHandler( listOf( TextNode(urc), ObjectNode(JsonNodeFactory.instance, mapOf(DL_FIELD to TextNode(receivedCommand)))) - val urcArray = mapper.valueToTree(urcList) + val urcArray: ArrayNode = mapper.valueToTree(urcList) newMessage.replace(URC_FIELD, urcArray) logger.debug { "Sending message with URC $urcArray" } return newMessage } + + companion object { + private const val DL_FIELD = "DL" + private const val URC_FIELD = "URC" + private const val URC_PSK_ERROR = "PSK:EQER" + private const val URC_PSK_SUCCESS = "PSK:SET" + } } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractor.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractor.kt index c22893c..c0b51d4 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractor.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractor.kt @@ -6,7 +6,6 @@ package org.gxf.crestdevicesimulator.simulator.response import org.gxf.crestdevicesimulator.simulator.response.command.exception.InvalidPskException object PskExtractor { - /** * Regex to split a valid PSK set command in 3 groups: Group 0 containing everything; Group 1 * containing the next 16 chars after PSK: this is only the key; Group 2 containing the next 64 @@ -23,11 +22,14 @@ object PskExtractor { private fun extractGroups(command: String): MatchGroupCollection { val matchingGroups = pskKeyHashSplitterRegex.findAll(command) - if (matchingGroups.none()) + if (matchingGroups.none()) { throw InvalidPskException("Command did not match psk set command") + } val groups = matchingGroups.first().groups - if (groups.size != 3) throw InvalidPskException("Command did not contain psk and hash") + if (groups.size != 3) { + throw InvalidPskException("Command did not contain psk and hash") + } return groups } } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskService.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskService.kt index 0164102..e6a58fd 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskService.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskService.kt @@ -3,9 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.response.command -import io.github.oshai.kotlinlogging.KotlinLogging -import jakarta.transaction.Transactional -import org.apache.commons.codec.digest.DigestUtils import org.gxf.crestdevicesimulator.configuration.AdvancedSingleIdentityPskStore import org.gxf.crestdevicesimulator.configuration.SimulatorProperties import org.gxf.crestdevicesimulator.simulator.data.entity.PreSharedKey @@ -13,8 +10,17 @@ import org.gxf.crestdevicesimulator.simulator.data.entity.PreSharedKeyStatus import org.gxf.crestdevicesimulator.simulator.data.repository.PskRepository import org.gxf.crestdevicesimulator.simulator.response.PskExtractor import org.gxf.crestdevicesimulator.simulator.response.command.exception.InvalidPskHashException + +import io.github.oshai.kotlinlogging.KotlinLogging +import jakarta.transaction.Transactional +import org.apache.commons.codec.digest.DigestUtils import org.springframework.stereotype.Service +/** + * @param pskRepository + * @param simulatorProperties + * @param pskStore + */ @Transactional @Service class PskService( @@ -22,7 +28,6 @@ class PskService( private val simulatorProperties: SimulatorProperties, private val pskStore: AdvancedSingleIdentityPskStore ) { - private val logger = KotlinLogging.logger {} fun preparePendingKey(body: String): PreSharedKey { @@ -48,17 +53,17 @@ class PskService( return setNewKeyForIdentity(activePreSharedKey, newPsk) } - private fun setNewKeyForIdentity(previousPSK: PreSharedKey, newKey: String): PreSharedKey { - val newVersion = previousPSK.revision + 1 + private fun setNewKeyForIdentity(previousPsk: PreSharedKey, newKey: String): PreSharedKey { + val newVersion = previousPsk.revision + 1 logger.debug { "Save new key for identity ${simulatorProperties.pskIdentity} with revision $newVersion and status PENDING" } return pskRepository.save( PreSharedKey( - previousPSK.identity, + previousPsk.identity, newVersion, newKey, - previousPSK.secret, + previousPsk.secret, PreSharedKeyStatus.PENDING)) } @@ -92,7 +97,7 @@ class PskService( pskRepository.findFirstByIdentityAndStatusOrderByRevisionDesc( identity, PreSharedKeyStatus.PENDING) - if (psk != null) { + psk?.let { psk.status = PreSharedKeyStatus.INVALID pskRepository.save(psk) } diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskException.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskException.kt index d01f946..af9546a 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskException.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskException.kt @@ -3,4 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.response.command.exception +/** + * @param message + */ class InvalidPskException(message: String) : Exception(message) diff --git a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskHashException.kt b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskHashException.kt index 4bf9708..c683ce7 100644 --- a/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskHashException.kt +++ b/application/src/main/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/exception/InvalidPskHashException.kt @@ -3,4 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.response.command.exception +/** + * @param message + */ class InvalidPskHashException(message: String) : Exception(message) diff --git a/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandlerTests.kt b/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandlerTests.kt index eb65adc..f8db587 100644 --- a/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandlerTests.kt +++ b/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/message/MessageHandlerTests.kt @@ -3,39 +3,28 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.message -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper -import org.assertj.core.api.Assertions.assertThat -import org.eclipse.californium.core.CoapClient import org.gxf.crestdevicesimulator.configuration.SimulatorProperties import org.gxf.crestdevicesimulator.simulator.CborFactory import org.gxf.crestdevicesimulator.simulator.coap.CoapClientService import org.gxf.crestdevicesimulator.simulator.response.CommandService import org.gxf.crestdevicesimulator.simulator.response.command.PskService + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.InjectMocks -import org.mockito.Mock import org.mockito.Mockito.`when` -import org.mockito.Spy -import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.mock import org.springframework.util.ResourceUtils -@ExtendWith(MockitoExtension::class) class MessageHandlerTests { - @Spy private val mapper = ObjectMapper() - - @Mock private lateinit var simulatorProperties: SimulatorProperties - - @Mock private lateinit var coapClient: CoapClient - - @Mock private lateinit var coapClientService: CoapClientService - - @Mock private lateinit var pskService: PskService - - @Mock private lateinit var commandService: CommandService - - @InjectMocks private lateinit var messageHandler: MessageHandler + private val mapper = ObjectMapper() + private val simulatorProperties: SimulatorProperties = mock() + private val coapClientService: CoapClientService = mock() + private val pskService: PskService = mock() + private val commandService: CommandService = mock() + private val messageHandler = MessageHandler(coapClientService, simulatorProperties, pskService, mapper, + commandService) @Test fun shouldSendInvalidCborWhenTheMessageTypeIsInvalidCbor() { diff --git a/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractorTest.kt b/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractorTest.kt index d33f46a..655396e 100644 --- a/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractorTest.kt +++ b/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/PskExtractorTest.kt @@ -8,34 +8,21 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource class PskExtractorTest { - - companion object { - private const val testHash = - "1234567890123456123456789012345612345678901234561234567890123456" - - private const val validPskCommand = - "!PSK:1234567891234567:${testHash};PSK:1234567891234567:${testHash}:SET" - private const val validPskCommandWithKeyWordsInKey = - "!PSK:PSKaSET1PSKd2SET:${testHash};PSK:PSKaSET1PSKd2SET:${testHash}:SET" - private const val invalidKeySizePskCommand = - "!PSK:1234:${testHash};PSK:1234:${testHash}:SET" - private const val notPskCommand = "NoPskCommandInThisString" - } - @ParameterizedTest @CsvSource( - "$validPskCommand, true", - "$validPskCommandWithKeyWordsInKey, true", - "$invalidKeySizePskCommand, false", - "$notPskCommand, false") - fun shouldReturnTrueWhenThereIsAPskCommandInString(pskCommand: String, isValid: Boolean) { + "$VALID_PSK_COMMAND, true", + "$VALID_PSK_COMMAND_WITH_KEY_WORDS_IN_KEY, true", + "$INVALID_KEY_SIZE_PSK_COMMAND, false", + "$NOT_PSK_COMMAND, false" + ) + fun shouldReturnTrueWhenThereIsApskCommandInString(pskCommand: String, isValid: Boolean) { val result = PskExtractor.hasPskSetCommand(pskCommand) assertThat(result).isEqualTo(isValid) } @ParameterizedTest @CsvSource( - "$validPskCommand, 1234567891234567", "$validPskCommandWithKeyWordsInKey, PSKaSET1PSKd2SET") + "$VALID_PSK_COMMAND, 1234567891234567", "$VALID_PSK_COMMAND_WITH_KEY_WORDS_IN_KEY, PSKaSET1PSKd2SET") fun shouldReturnPskKeyFromValidPskCommand(pskCommand: String, expectedKey: String) { val result = PskExtractor.extractKeyFromCommand(pskCommand) @@ -43,10 +30,20 @@ class PskExtractorTest { } @ParameterizedTest - @CsvSource("$validPskCommand, $testHash", "$validPskCommandWithKeyWordsInKey, $testHash") + @CsvSource("$VALID_PSK_COMMAND, $TEST_HASH", "$VALID_PSK_COMMAND_WITH_KEY_WORDS_IN_KEY, $TEST_HASH") fun shouldReturnHashFromValidPskCommand(pskCommand: String, expectedHash: String) { val result = PskExtractor.extractHashFromCommand(pskCommand) assertThat(result).isEqualTo(expectedHash) } + + @Suppress("WRONG_DECLARATIONS_ORDER") // Prevent diktat from moving TEST_HASH + companion object { + private const val TEST_HASH = "1234567890123456123456789012345612345678901234561234567890123456" + private const val INVALID_KEY_SIZE_PSK_COMMAND = "!PSK:1234:$TEST_HASH;PSK:1234:$TEST_HASH:SET" + private const val NOT_PSK_COMMAND = "NoPskCommandInThisString" + private const val VALID_PSK_COMMAND = "!PSK:1234567891234567:$TEST_HASH;PSK:1234567891234567:$TEST_HASH:SET" + private const val VALID_PSK_COMMAND_WITH_KEY_WORDS_IN_KEY = + "!PSK:PSKaSET1PSKd2SET:$TEST_HASH;PSK:PSKaSET1PSKd2SET:$TEST_HASH:SET" + } } diff --git a/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskServiceTest.kt b/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskServiceTest.kt index 721edde..cdca168 100644 --- a/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskServiceTest.kt +++ b/application/src/test/kotlin/org/gxf/crestdevicesimulator/simulator/response/command/PskServiceTest.kt @@ -3,9 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 package org.gxf.crestdevicesimulator.simulator.response.command -import org.apache.commons.codec.digest.DigestUtils -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.catchException import org.gxf.crestdevicesimulator.configuration.AdvancedSingleIdentityPskStore import org.gxf.crestdevicesimulator.configuration.SimulatorProperties import org.gxf.crestdevicesimulator.simulator.data.entity.PreSharedKey @@ -13,52 +10,36 @@ import org.gxf.crestdevicesimulator.simulator.data.entity.PreSharedKeyStatus import org.gxf.crestdevicesimulator.simulator.data.repository.PskRepository import org.gxf.crestdevicesimulator.simulator.response.command.exception.InvalidPskException import org.gxf.crestdevicesimulator.simulator.response.command.exception.InvalidPskHashException + +import org.apache.commons.codec.digest.DigestUtils +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchException import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.Answers -import org.mockito.InjectMocks -import org.mockito.Mock -import org.mockito.Mockito.lenient -import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any +import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -@ExtendWith(MockitoExtension::class) class PskServiceTest { - - @Mock private lateinit var pskRepository: PskRepository - - @Mock private lateinit var simulatorProperties: SimulatorProperties - - @Mock(answer = Answers.CALLS_REAL_METHODS) - private lateinit var pskStore: AdvancedSingleIdentityPskStore - - @InjectMocks private lateinit var pskService: PskService - private val newKey = "7654321987654321" - private val oldKey = "1234567891234567" - private val secret = "secret" - private val identity = "867787050253370" - private val oldRevision = 0 - private val newRevision = 1 + private val pskRepository: PskRepository = mock() + private val simulatorProperties: SimulatorProperties = mock() + private val pskStore = AdvancedSingleIdentityPskStore(oldKey) + private val pskService = PskService(pskRepository, simulatorProperties, pskStore) @BeforeEach fun setup() { whenever(simulatorProperties.pskIdentity).thenReturn(identity) val psk = PreSharedKey(identity, oldRevision, oldKey, secret, PreSharedKeyStatus.ACTIVE) - lenient().whenever(simulatorProperties.pskIdentity).thenReturn(identity) - lenient() - .whenever( - pskRepository.findFirstByIdentityAndStatusOrderByRevisionDesc( - identity, PreSharedKeyStatus.ACTIVE)) + whenever(simulatorProperties.pskIdentity).thenReturn(identity) + whenever(pskRepository.findFirstByIdentityAndStatusOrderByRevisionDesc(identity, PreSharedKeyStatus.ACTIVE)) .thenReturn(psk) pskStore.key = oldKey } @@ -66,7 +47,7 @@ class PskServiceTest { @Test fun shouldSetNewPskInStoreWhenTheKeyIsValid() { val expectedHash = DigestUtils.sha256Hex("$secret$newKey") - val pskCommand = "!PSK:$newKey:${expectedHash};PSK:$newKey:${expectedHash}:SET" + val pskCommand = "!PSK:$newKey:$expectedHash;PSK:$newKey:$expectedHash:SET" val psk = PreSharedKey(identity, newRevision, newKey, secret, PreSharedKeyStatus.PENDING) whenever(pskRepository.save(any())).thenReturn(psk) @@ -78,7 +59,7 @@ class PskServiceTest { @Test fun shouldThrowErrorPskCommandIsInvalid() { val invalidHash = DigestUtils.sha256Hex("invalid") - val pskCommand = "!PSK:$oldKey;PSK:$oldKey:${invalidHash}:SET" + val pskCommand = "!PSK:$oldKey;PSK:$oldKey:$invalidHash:SET" val thrownException = catchException { pskService.preparePendingKey(pskCommand) } @@ -90,7 +71,7 @@ class PskServiceTest { @Test fun shouldThrowErrorWhenHashDoesNotMatch() { val invalidHash = DigestUtils.sha256Hex("invalid") - val pskCommand = "!PSK:$oldKey:$invalidHash;PSK:$oldKey:${invalidHash}:SET" + val pskCommand = "!PSK:$oldKey:$invalidHash;PSK:$oldKey:$invalidHash:SET" val thrownException = catchException { pskService.preparePendingKey(pskCommand) } diff --git a/build.gradle.kts b/build.gradle.kts index f92be74..9e32119 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ subprojects { extensions.configure { kotlin { // by default the target is every '.kt' and '.kts' file in the java source sets - ktfmt().dropboxStyle() + diktat("2.0.0").configFile { "${project.rootDir}/diktat-analysis.yml" } licenseHeaderFile( "${project.rootDir}/license-template.kt", "package") diff --git a/diktat-analysis.yml b/diktat-analysis.yml new file mode 100644 index 0000000..f17c338 --- /dev/null +++ b/diktat-analysis.yml @@ -0,0 +1,568 @@ +# Common configuration +- name: DIKTAT_COMMON + configuration: + # put your package name here - it will be autofixed and checked + domainName: org.gxf.crestdevicesimulator + testDirs: test, integrationTest + # expected values: disabledChapters: "Naming, Comments, General, Variables, Functions, Classes" + # or: "1, 2, 3, 4, 5, 6" + disabledChapters: "" + kotlinVersion: 1.9 + srcDirectories: "main" +# Checks that the Class/Enum/Interface name matches Pascal case +- name: CLASS_NAME_INCORRECT + enabled: true + # all code blocks with MyAnnotation will be ignored and not checked + ignoreAnnotated: [ MyAnnotation ] +# Checks that CONSTANT (treated as const val from companion object or class level) is in non UPPER_SNAKE_CASE +- name: CONSTANT_UPPERCASE + enabled: true +# Checks that enum value is in upper SNAKE_CASE or in PascalCase depending on the config. UPPER_SNAKE_CASE is the default, but can be changed by 'enumStyle' config +- name: ENUM_VALUE + enabled: true + configuration: + # Two options: SNAKE_CASE (default), PascalCase + enumStyle: SNAKE_CASE +# Checks that class which extends any Exception class has Exception suffix +- name: EXCEPTION_SUFFIX + enabled: true +# Checks that file name has extension +- name: FILE_NAME_INCORRECT + enabled: true +# Checks that file name matches class name, if it is only one class in file +- name: FILE_NAME_MATCH_CLASS + enabled: true +# Checks that functions/methods which return boolean have special prefix like "is/should/e.t.c" +- name: FUNCTION_BOOLEAN_PREFIX + enabled: true + configuration: + allowedPrefixes: "" # A list of functions that return boolean and are allowed to use. Input is in a form "foo, bar". +# Checks that function/method name is in lowerCamelCase +- name: FUNCTION_NAME_INCORRECT_CASE + enabled: true +# Checks that typealias name is in PascalCase +- name: TYPEALIAS_NAME_INCORRECT_CASE + enabled: true +# Checks that generic name doesn't contain more than 1 letter (capital). It can be followed by numbers, example: T12, T +- name: GENERIC_NAME + enabled: true +# Identifier length should be in range [2,64] except names that used in industry like {i, j} and 'e' for catching exceptions +- name: IDENTIFIER_LENGTH + enabled: true +# Checks that the object matches PascalCase +- name: OBJECT_NAME_INCORRECT + enabled: true +# Checks that package name is in correct (lower) case +- name: PACKAGE_NAME_INCORRECT_CASE + enabled: true +# Checks that package name starts with the company's domain +- name: PACKAGE_NAME_INCORRECT_PREFIX + enabled: false +# Checks that package name does not have incorrect symbols like underscore or non-ASCII letters/digits +- name: PACKAGE_NAME_INCORRECT_SYMBOLS + enabled: true +# Checks that the path for a file matches with a package name +- name: PACKAGE_NAME_INCORRECT_PATH + enabled: false +# Checks that package name is in the file +- name: PACKAGE_NAME_MISSING + enabled: true +# Checks that variable does not have prefix (like mVariable or M_VARIABLE) +- name: VARIABLE_HAS_PREFIX + enabled: true +# Checks that variable does not contain one single letter, only exceptions are fixed names that used in industry like {i, j} +- name: VARIABLE_NAME_INCORRECT + enabled: true +# Checks that the name of variable is in lowerCamelCase and contains only ASCII letters +- name: VARIABLE_NAME_INCORRECT_FORMAT + enabled: true +# Checks that functions have kdoc +- name: MISSING_KDOC_ON_FUNCTION + # DEFAULT + # enabled: true + # MODIFIED + enabled: false +# Checks that on file level internal or public class or function has missing KDoc +- name: MISSING_KDOC_TOP_LEVEL + # DEFAULT + # enabled: true + # MODIFIED + enabled: false +# Checks that accessible internal elements (protected, public, internal) in a class are documented +- name: MISSING_KDOC_CLASS_ELEMENTS + # DEFAULT + # enabled: true + # MODIFIED + enabled: false +# Checks that accessible method parameters are documented in KDoc +- name: KDOC_WITHOUT_PARAM_TAG + enabled: true +# Checks that accessible method explicit return type is documented in KDoc +- name: KDOC_WITHOUT_RETURN_TAG + enabled: true +# Checks that accessible method throw keyword is documented in KDoc +- name: KDOC_WITHOUT_THROWS_TAG + enabled: true +# Checks that KDoc is not empty +- name: KDOC_EMPTY_KDOC + enabled: true +# Checks that underscore is correctly used to split package naming +- name: INCORRECT_PACKAGE_SEPARATOR + enabled: true +# Checks that code block doesn't contain kdoc comments +- name: COMMENTED_BY_KDOC + enabled: true +# Checks that there is no @deprecated tag in kdoc +- name: KDOC_NO_DEPRECATED_TAG + enabled: true +# Checks that there is no empty content in kdoc tags +- name: KDOC_NO_EMPTY_TAGS + enabled: true +# Checks that there is only one space after kdoc tag +- name: KDOC_WRONG_SPACES_AFTER_TAG + enabled: true +# Checks tags order in kDoc. `@param`, `@return`, `@throws` +- name: KDOC_WRONG_TAGS_ORDER + enabled: true +# Checks that there is no newline of empty KDoc line (with leading asterisk) between `@param`, `@return`, `@throws` tags +- name: KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS + enabled: true +# Checks that block of tags @param, @return, @throws is separated from previous part of KDoc by exactly one empty line +- name: KDOC_NEWLINES_BEFORE_BASIC_TAGS + enabled: true +# Checks that special tags `@apiNote`, `@implNote`, `@implSpec` have exactly one empty line after +- name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS + enabled: true +# Checks that kdoc does not contain @author tag or date +- name: KDOC_CONTAINS_DATE_OR_AUTHOR + enabled: true + configuration: + versionRegex: \d+\.\d+\.\d+[-.\w\d]* +# Checks that KDoc does not contain single line with words 'return', 'get' or 'set' +- name: KDOC_TRIVIAL_KDOC_ON_FUNCTION + enabled: true +# Checks that there is newline after header KDoc +- name: HEADER_WRONG_FORMAT + enabled: true +# Checks that file with zero or >1 classes has header KDoc +- name: HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE + enabled: true +# Checks that copyright exists on top of file and is properly formatted (as a block comment) +- name: HEADER_MISSING_OR_WRONG_COPYRIGHT + # DEFAULT + # enabled: true + # MODIFIED, copyright header is handled by spotless + enabled: false + configuration: + isCopyrightMandatory: false + copyrightText: 'Copyright (c) Your Company Name Here. 2010-;@currYear;' +# Checks that header kdoc is located before package directive +- name: HEADER_NOT_BEFORE_PACKAGE + enabled: true +# Checks that file does not contain lines > maxSize +- name: FILE_IS_TOO_LONG + enabled: true + configuration: + # number of lines + maxSize: '2000' +# Checks that file does not contain commented out code +- name: COMMENTED_OUT_CODE + enabled: true +# Checks that file does not contain only comments, imports and package directive +- name: FILE_CONTAINS_ONLY_COMMENTS + enabled: true +# Orders imports alphabetically +- name: FILE_UNORDERED_IMPORTS + enabled: true + configuration: + # use logical imports grouping with sorting inside of a group + useRecommendedImportsOrder: true +# Checks that general order of code parts is right +- name: FILE_INCORRECT_BLOCKS_ORDER + enabled: true +# Checks that there is exactly one line between code blocks +- name: FILE_NO_BLANK_LINE_BETWEEN_BLOCKS + enabled: true +# Checks that there is no wildcard imports. Exception: allowedWildcards +- name: FILE_WILDCARD_IMPORTS + enabled: true + configuration: + allowedWildcards: "" # Allowed wildcards for imports (e.g. "import com.saveourtool.diktat.*, import org.jetbrains.kotlin.*") + useRecommendedImportsOrder: true +# Checks unused imports +- name: UNUSED_IMPORT + enabled: true + configuration: + deleteUnusedImport: true +# Checks that braces are used in if, else, when, for, do, and while statements. Exception: single line ternary operator statement +- name: NO_BRACES_IN_CONDITIONALS_AND_LOOPS + enabled: true +# Checks that the declaration part of a class-like code structures (class/interface/etc.) is in the proper order +- name: WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES + enabled: true +# Checks that properties with comments are separated by a blank line +- name: BLANK_LINE_BETWEEN_PROPERTIES + enabled: true +# Checks top level order +- name: TOP_LEVEL_ORDER + enabled: true +# Checks that non-empty code blocks with braces follow the K&R style (1TBS or OTBS style) +- name: BRACES_BLOCK_STRUCTURE_ERROR + enabled: true + configuration: + openBraceNewline: 'True' + closeBraceNewline: 'True' +# Checks that indentation is correct +- name: WRONG_INDENTATION + enabled: true + configuration: + # Is newline at the end of a file needed + newlineAtEnd: true + # If true: in parameter list when parameters are split by newline they are indented with two indentations instead of one + extendedIndentOfParameters: false + # If true: if first parameter in parameter list is on the same line as opening parenthesis, then other parameters can be aligned with it + alignedParameters: true + # If true, expression bodies which begin on a separate line are indented + # using a continuation indent. The default is false. + # + # This flag is called CONTINUATION_INDENT_FOR_EXPRESSION_BODIES in IDEA and + # ij_kotlin_continuation_indent_for_expression_bodies in .editorconfig. + extendedIndentForExpressionBodies: false + # If true: if expression is split by newline after operator like +/-/`*`, then the next line is indented with two indentations instead of one + extendedIndentAfterOperators: true + # If true: when dot qualified expression starts on a new line, this line will be indented with two indentations instead of one + extendedIndentBeforeDot: false + # The indentation size for each file + indentationSize: 4 +# Checks that there is no empty blocks in a file. +# If allowEmptyBlocks is true, checks that it follows correct style (have a newline) +- name: EMPTY_BLOCK_STRUCTURE_ERROR + enabled: true + configuration: + # Whether a newline after `{` is required in an empty block + styleEmptyBlockWithNewline: 'True' + allowEmptyBlocks: 'False' +# Checks that there is no more than one statement per line +- name: MORE_THAN_ONE_STATEMENT_PER_LINE + enabled: true +# Checks that the line length is < lineLength parameter +- name: LONG_LINE + enabled: true + configuration: + lineLength: '120' +# Checks that semicolons are not used at the end of a line +- name: REDUNDANT_SEMICOLON + enabled: true +# Checks that line breaks follow code style guide: rule 3.6 +- name: WRONG_NEWLINES + enabled: true + configuration: + # If the number of parameters on one line is more than this threshold, all parameters will be placed on separate lines. + maxParametersInOneLine: 2 + # 3 by default. + maxCallsInOneLine: 3 +# Checks trailing comma +- name: TRAILING_COMMA + enabled: true + configuration: + # VALUE_ARGUMENT + valueArgument: false + # VALUE_PARAMETER + valueParameter: false + # REFERENCE_EXPRESSION + indices: false + # WHEN_CONDITION_WITH_EXPRESSION + whenConditions: false + # STRING_TEMPLATE + collectionLiteral: false + # TYPE_PROJECTION + typeArgument: false + # TYPE_PARAMETER + typeParameter: false + # DESTRUCTURING_DECLARATION_ENTRY + destructuringDeclaration: false +# Checks that there are not too many consecutive spaces in line +- name: TOO_MANY_CONSECUTIVE_SPACES + enabled: true + configuration: + # Maximum allowed number of consecutive spaces (not counting indentation) + maxSpaces: '1' + # Whether formatting for enums should be kept without checking + saveInitialFormattingForEnums: false +# Inspection that checks if a long dot qualified expression is used in condition or as an argument +- name: COMPLEX_EXPRESSION + enabled: true +# Checks that blank lines are used correctly. +# For example: triggers when there are too many blank lines between function declaration +- name: TOO_MANY_BLANK_LINES + enabled: true +# Checks that usage of horizontal spaces doesn't violate code style guide +- name: WRONG_WHITESPACE + enabled: true +# Checks that backticks (``) are not used in the identifier name, except the case when it is test method (marked with @Test annotation) +- name: BACKTICKS_PROHIBITED + enabled: true +# Checks that a single line concatenation of strings is not used +- name: STRING_CONCATENATION + enabled: true +# Checks that each when statement have else in the end +- name: WHEN_WITHOUT_ELSE + enabled: true +# Checks that annotation is on a single line +- name: ANNOTATION_NEW_LINE + enabled: true +# Checks that method annotated with `Preview` annotation is private and has Preview suffix +- name: PREVIEW_ANNOTATION + enabled: true +# Checks that enum structure is correct: enum entries should be separated by comma and line break and last entry should have semicolon in the end. +- name: ENUMS_SEPARATED + enabled: true +# Checks that value on integer or float constant is not too big +- name: LONG_NUMERICAL_VALUES_SEPARATED + enabled: true + configuration: + # Maximum number of digits which are not split + maxNumberLength: '5' + # Maximum number of digits between separators + maxBlockLength: '3' +# Checks magic number +- name: MAGIC_NUMBER + enabled: true + configuration: + # Ignore numbers from test + ignoreTest: "true" + # Ignore numbers + ignoreNumbers: "-1, 1, 0, 2, 0U, 1U, 2U, -1L, 0L, 1L, 2L, 0UL, 1UL, 2UL" + # Is ignore override hashCode function + ignoreHashCodeFunction: "true" + # Is ignore property + ignorePropertyDeclaration: "false" + # Is ignore local variable + ignoreLocalVariableDeclaration: "false" + # Is ignore value parameter + ignoreValueParameter: "true" + # Is ignore constant + ignoreConstantDeclaration: "true" + # Is ignore property in companion object + ignoreCompanionObjectPropertyDeclaration: "true" + # Is ignore numbers in enum + ignoreEnums: "false" + # Is ignore number in ranges + ignoreRanges: "false" + # Is ignore number in extension function + ignoreExtensionFunctions: "false" + # Is ignore number in pairs created using to + ignorePairsCreatedUsingTo: "false" +# Checks that order of enum values or constant property inside companion is correct +- name: WRONG_DECLARATIONS_ORDER + enabled: true + configuration: + # Whether enum members should be sorted alphabetically + sortEnum: true + # Whether class properties should be sorted alphabetically + sortProperty: true +# Checks that multiple modifiers sequence is in the correct order +- name: WRONG_MULTIPLE_MODIFIERS_ORDER + enabled: true +# Checks that identifier has appropriate name (See table of rule 1.2 part 6) +- name: CONFUSING_IDENTIFIER_NAMING + enabled: true +# Checks year in the copyright +- name: WRONG_COPYRIGHT_YEAR + enabled: true +# Inspection that checks if local variables are declared close to the first usage site +- name: LOCAL_VARIABLE_EARLY_DECLARATION + enabled: true +# Try to avoid initialize val by null (e.g. val a: Int? = null -> val a: Int = 0) +- name: NULLABLE_PROPERTY_TYPE + enabled: true +# Inspection that checks if there is a blank line before kDoc and none after +- name: WRONG_NEWLINES_AROUND_KDOC + enabled: true +# Inspection that checks if there is no blank lines before first comment +- name: FIRST_COMMENT_NO_BLANK_LINE + enabled: true +# Inspection that checks if there are blank lines between code and comment and between code start token and comment's text +- name: COMMENT_WHITE_SPACE + enabled: true + configuration: + maxSpacesBeforeComment: 2 + maxSpacesInComment: 1 +# Inspection that checks if all comment's are inside if-else code blocks. Exception is general if comment +- name: IF_ELSE_COMMENTS + enabled: true +# Type aliases provide alternative names for existing types when type's reference text is longer 25 chars +- name: TYPE_ALIAS + enabled: true + configuration: + typeReferenceLength: '25' # max length of type reference +# Checks if casting can be omitted +- name: SMART_CAST_NEEDED + enabled: true +# Checks that variables of generic types have explicit type declaration +- name: GENERIC_VARIABLE_WRONG_DECLARATION + enabled: true +# Inspection that checks if string template has redundant curly braces +- name: STRING_TEMPLATE_CURLY_BRACES + enabled: true +# Variables with `val` modifier - are immutable (read-only). Usage of such variables instead of `var` variables increases +# robustness and readability of code, because `var` variables can be reassigned several times in the business logic. +# This rule prohibits usage of `var`s as local variables - the only exception is accumulators and counters +- name: SAY_NO_TO_VAR + enabled: true +# Inspection that checks if string template has redundant quotes +- name: STRING_TEMPLATE_QUOTES + enabled: true +# Check if there are redundant nested if-statements, which could be collapsed into a single one by concatenating their conditions +- name: COLLAPSE_IF_STATEMENTS + enabled: true + configuration: + startCollapseFromNestedLevel: 2 +# Checks that floating-point values are not used in arithmetic expressions +- name: FLOAT_IN_ACCURATE_CALCULATIONS + enabled: true +# Checks that function length isn't too long +- name: TOO_LONG_FUNCTION + enabled: true + configuration: + maxFunctionLength: '30' # max length of function + isIncludeHeader: 'false' # count function's header +# Warns if there are nested functions +- name: AVOID_NESTED_FUNCTIONS + enabled: true +# Checks that lambda inside function parameters is in the end +- name: LAMBDA_IS_NOT_LAST_PARAMETER + enabled: true +# Checks that function doesn't contains too many parameters +- name: TOO_MANY_PARAMETERS + enabled: true + configuration: + # DEFAULT + # maxParameterListSize: '5' # max parameters size + # MODIFIED + maxParameterListSize: '7' +# Checks that function doesn't have too many nested blocks +- name: NESTED_BLOCK + enabled: true + configuration: + maxNestedBlockQuantity: '4' +# Checks that function use default values, instead overloading +- name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS + enabled: true +# Checks that using runBlocking inside async block code +- name: RUN_BLOCKING_INSIDE_ASYNC + enabled: true +# Checks that property in constructor doesn't contain comment +- name: KDOC_NO_CONSTRUCTOR_PROPERTY + enabled: true + configuration: + isParamTagsForParameters: true # create param tags for parameters without val or var + isParamTagsForPrivateProperties: true # create param tags for private properties + isParamTagsForGenericTypes: true # create param tags for generic types +# Checks that the long lambda has parameters +- name: TOO_MANY_LINES_IN_LAMBDA + enabled: true + configuration: + maxLambdaLength: 10 # max length of lambda without parameters +# Checks that using unnecessary, custom label +- name: CUSTOM_LABEL + enabled: true +# Check that lambda with inner lambda doesn't use implicit parameter +- name: PARAMETER_NAME_IN_OUTER_LAMBDA + enabled: true +# Checks that property in KDoc present in class +- name: KDOC_EXTRA_PROPERTY + enabled: true +# There's a property in KDoc which is already present +- name: KDOC_DUPLICATE_PROPERTY + enabled: true +# Checks that KDoc in constructor has property tag but with comment inside constructor +- name: KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT + enabled: true +# if a class has single constructor, it should be converted to a primary constructor +- name: SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY + enabled: true +# Checks if class can be made as data class +- name: USE_DATA_CLASS + enabled: true +# Checks that never use the name of a variable in the custom getter or setter +- name: WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR + enabled: true +# Checks that classes have only one init block +- name: MULTIPLE_INIT_BLOCKS + enabled: true +# Checks that there are abstract functions in abstract class +- name: CLASS_SHOULD_NOT_BE_ABSTRACT + enabled: true +# Checks if there are any trivial getters or setters +- name: TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED + enabled: true +# Checks that no custom getters and setters are used for properties. It is a more wide rule than TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED +# Kotlin compiler automatically generates `get` and `set` methods for properties and also lets the possibility to override it. +# But in all cases it is very confusing when `get` and `set` are overridden for a developer who uses this particular class. +# Developer expects to get the value of the property, but receives some unknown value and some extra side effect hidden by the custom getter/setter. +# Use extra functions for it instead. +- name: CUSTOM_GETTERS_SETTERS + enabled: true +# Checks if null-check was used explicitly (for example: if (a == null)) +# Try to avoid explicit null checks (explicit comparison with `null`) +# Kotlin is declared as [Null-safe](https://kotlinlang.org/docs/reference/null-safety.html) language. +# But Kotlin architects wanted Kotlin to be fully compatible with Java, that's why `null` keyword was also introduced in Kotlin. +# There are several code-structures that can be used in Kotlin to avoid null-checks. For example: `?:`, `.let {}`, `.also {}`, e.t.c +- name: AVOID_NULL_CHECKS + enabled: true +# Checks if class instantiation can be wrapped in `apply` for better readability +- name: COMPACT_OBJECT_INITIALIZATION + enabled: true +# Checks explicit supertype qualification +- name: USELESS_SUPERTYPE + enabled: true +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE + enabled: true +# Checks if there is empty primary constructor +- name: EMPTY_PRIMARY_CONSTRUCTOR + enabled: true +# In case of not using field keyword in property accessors, +# there should be explicit backing property with the name of real property +# Example: val table get() {if (_table == null) ...} -> table should have _table +- name: NO_CORRESPONDING_PROPERTY + enabled: true +# Checks if there is class/object that can be replace with extension function +- name: AVOID_USING_UTILITY_CLASS + enabled: true +# If there is stateless class it is preferred to use object +- name: OBJECT_IS_PREFERRED + enabled: true +# If there exists negated version of function you should prefer it instead of !functionCall +- name: INVERSE_FUNCTION_PREFERRED + enabled: true +# Checks if class can be converted to inline class +- name: INLINE_CLASS_CAN_BE_USED + enabled: true +# If file contains class, then it can't contain extension functions for the same class +- name: EXTENSION_FUNCTION_WITH_CLASS + enabled: true +# Check if kts script contains other functions except run code +- name: RUN_IN_SCRIPT + enabled: true +# Check if boolean expression can be simplified +- name: COMPLEX_BOOLEAN_EXPRESSION + enabled: true +# Check if range can replace with until or `rangeTo` function with range +- name: CONVENTIONAL_RANGE + enabled: true + configuration: + isRangeToIgnore: false +# Check if there is a call of print()\println() or console.log(). Assumption that it's a debug print +- name: DEBUG_PRINT + enabled: true +# Check that typealias name is in PascalCase +- name: TYPEALIAS_NAME_INCORRECT_CASE + enabled: true +# Should change property length - 1 to property lastIndex +- name: USE_LAST_INDEX + enabled: true +# Only properties from the primary constructor should be documented in a @property tag in class KDoc +- name: KDOC_NO_CLASS_BODY_PROPERTIES_IN_HEADER + enabled: true \ No newline at end of file