Commit 01b8535d authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Recovered legacy Gateway code

parent 522300c4
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
# Copyright 2022-2024 ETSI OSG/SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM zenika/kotlin:1.4-jdk12

# Make working directory move to it and copy DLT Gateway code
RUN mkdir -p /var/teraflow/dlt/gateway
WORKDIR /var/teraflow/dlt/gateway
COPY src/dlt/gateway/. ./

# Make directory for proto files and copy them
RUN mkdir proto
COPY proto/*.proto ./proto/

# Build DLT Gateway
RUN ./gradlew build

EXPOSE 50051

# Create entrypoint.sh script
RUN echo "#!/bin/sh" > /entrypoint.sh
RUN echo "echo 195.37.154.24 peer0.org1.example.com >> /etc/hosts" >> /entrypoint.sh
RUN echo "echo 195.37.154.24 peer0.org2.example.com >> /etc/hosts" >> /entrypoint.sh
RUN echo "echo 195.37.154.24 orderer0.example.com   >> /etc/hosts" >> /entrypoint.sh
RUN echo "cd /var/teraflow/dlt/gateway" >> /entrypoint.sh
RUN echo "./gradlew runServer" >> /entrypoint.sh
RUN chmod +x /entrypoint.sh

# Gateway entry point
ENTRYPOINT ["sh", "/entrypoint.sh"]
+134 −0
Original line number Diff line number Diff line
```
     NEC Laboratories Europe GmbH

     PROPRIETARY INFORMATION

 The software and its source code contain valuable trade secrets and
 shall be maintained in confidence and treated as confidential
 information. The software may only be used for evaluation and/or
 testing purposes, unless otherwise explicitly stated in a written
 agreement with NEC Laboratories Europe GmbH.

 Any unauthorized publication, transfer to third parties or
 duplication of the object or source code - either totally or in
 part - is strictly prohibited.

          Copyright (c) 2022 NEC Laboratories Europe GmbH
          All Rights Reserved.

 Authors: Konstantin Munichev <konstantin.munichev@neclab.eu>


 NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE
 WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND
 THE ACCOMPANYING DOCUMENTATION.

 NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC
 Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR
 ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR
 LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF
 INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL,
 INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF
 OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe
 GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

 THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY.
 ```

# DLT module guide

## General information
The DLT module is used to provide access to the underlying Fabric deployment. It allows clients
to add, retrieve, modify and delete blockchain-backed data, essentially working as a key-value
database. External clients should use gRPC API to communicate with this service, its detailed
description available below.

## Code structure
The whole DLT module consists of several packages:
- fabric package
- http package
- proto package
- client example

### Fabric package
The most important class in this package is `FabricConnector`. First, it establishes connection
with the underlying Fabric network using Java Gateway SDK. After that, it could be used as a
CRUD interface.
Other files contain auxiliary code for `FabricConnector` which allows it to register/enroll
users and to obtain smart contract instances.

### Grpc package
Contains server side gRPC handler. It accepts requests from the outside and performs the
requested operation. For the more detailed description see Proto package description right below.

### Proto package
The proto package contains `dlt.proto` file which defines gRPC service `DltService` API and messages
it uses. There are 3 main functions: `RecordToDlt` which allows to create/modify/delete data,
`GetFromDlt` which returns already written data and `SubscribeToDlt` which allows clients subscribe
for future create/modify/delete events with provided filters.
Other proto files don't play any significant role and could be safely ignored by end users.

### Client example
This code is not necessary to the service, but it could be used to test the service. It contains
a sample gRPC client which connects the service and perform all the CRUD operations. 

# Fabric deployment notes

## General notes
Current Fabric deployment uses Fabric test network with some additional helping scripts on top of it.
To start the network just run the `raft.sh` from `blockchain/scripts` directory. Use `stop.sh`
when you need to stop the network.

## Server start preparations
To run the server it's necessary to copy certificate file
`fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem`
to the config folder (replacing the existing one). Also, it's necessary to copy `scripts/connection-org1.json`
file (again, replacing the old one). After copying, it must be edited. First, all `localhost` entrances
should be replaced with `teraflow.nlehd.de`. Second, `channel` section at the end of the file should be removed.
This should be done after every restart of the Fabric network.

## Fabric configuration
Even though a test network is easy to deploy and use it's better to perform a custom configuration
for a production deployment. In practice every participating organization will likely prefer to have
its own Peer/Orderer/CA instances to prevent possible dependency on any other participants. This leads
not only to a better privacy/availability/security in general but also to the more complicated
deployment process as a side effect. Here we provide a very brief description of the most important points.

### Organizations
Organization represents a network participant, which can be an individual, a large corporation or any other
entity. Each organization has its own CAs, orderers and peers. The recommendation here is to create an
organization entity for every independent participant and then decide how many CAs/peers/orderers does
every organization need and which channels should it has access to based on the exact project's goals. 

### Channels
Each channel represents an independent ledger with its own genesis block. Each transaction is executed
on a specific channel, and it's possible to define which organization has access to a given channel.
As a result channels are a pretty powerful privacy mechanism which allows to limit access to the private
data between organization.

### Certificate authorities, peers and orderers
Certificate authorities (CA) are used to generate crypto materials for each organization. Two types of CA
exist: one is used to generate the certificates of the admin, the MSP and certificates of non-admin users.
Another type of CA is used to generate TLS certificates. As a result it's preferable to have at least two
CAs for every organization.

Peers are entities which host ledgers and smart contracts. They communicate with applications and orderers,
receiving chaincode invocations (proposals), invoking chaincode, updating ledger when necessary and
returning result of execution. Peers can handle one or many ledgers, depending on the configuration. It's
very use case specific how many peers are necessary to the exact deployment.

Orderers are used to execute a consensus in a distributing network making sure that every channel participant
has the same blocks with the same data. The default consensus algorithm is Raft which provides only a crash
fault tolerance.

### Conclusion
As you can see, configuration procedure for Fabric is pretty tricky and includes quite a lot of entities.
In real world it will very likely involve participants from multiple organizations each of them performing
its own part of configuration.

As a further reading it's recommended to start with the
[official deployment guide](https://hyperledger-fabric.readthedocs.io/en/release-2.2/deployment_guide_overview.html).
It contains a high level overview of a deployment process as well as links to the detailed descriptions to
CA/Peer/Orderer configuration descriptions.
 No newline at end of file
+147 −0
Original line number Diff line number Diff line
//     NEC Laboratories Europe GmbH
//
//     PROPRIETARY INFORMATION
//
// The software and its source code contain valuable trade secrets and
// shall be maintained in confidence and treated as confidential
// information. The software may only be used for evaluation and/or
// testing purposes, unless otherwise explicitly stated in a written
// agreement with NEC Laboratories Europe GmbH.
//
// Any unauthorized publication, transfer to third parties or
// duplication of the object or source code - either totally or in
// part - is strictly prohibited.
//
//          Copyright (c) 2021 NEC Laboratories Europe GmbH
//          All Rights Reserved.
//
// Authors: Konstantin Munichev <konstantin.munichev@neclab.eu>
//
//
// NEC Laboratories Europe GmbH DISCLAIMS ALL WARRANTIES, EITHER
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND THE
// WARRANTY AGAINST LATENT DEFECTS, WITH RESPECT TO THE PROGRAM AND
// THE ACCOMPANYING DOCUMENTATION.
//
// NO LIABILITIES FOR CONSEQUENTIAL DAMAGES: IN NO EVENT SHALL NEC
// Laboratories Europe GmbH or ANY OF ITS SUBSIDIARIES BE LIABLE FOR
// ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR
// LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF
// INFORMATION, OR OTHER PECUNIARY LOSS AND INDIRECT, CONSEQUENTIAL,
// INCIDENTAL, ECONOMIC OR PUNITIVE DAMAGES) ARISING OUT OF THE USE OF
// OR INABILITY TO USE THIS PROGRAM, EVEN IF NEC Laboratories Europe
// GmbH HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
//
// THIS HEADER MAY NOT BE EXTRACTED OR MODIFIED IN ANY WAY.

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

import com.google.protobuf.gradle.generateProtoTasks
import com.google.protobuf.gradle.id
import com.google.protobuf.gradle.plugins
import com.google.protobuf.gradle.protobuf
import com.google.protobuf.gradle.protoc

ext["grpcVersion"] = "1.47.0"
ext["grpcKotlinVersion"] = "1.3.0" // CURRENT_GRPC_KOTLIN_VERSION
ext["protobufVersion"] = "3.20.1"
ext["ktorVersion"] = "1.6.5"

plugins {
    kotlin("jvm") version "1.6.21"
    kotlin("plugin.serialization") version "1.4.21"
    id("com.google.protobuf") version "0.8.18"
    application
}

group = "eu.neclab"
version = "1.0-SNAPSHOT"

repositories {
    mavenLocal()
    google()
    mavenCentral()
}

dependencies {
    implementation(kotlin("stdlib-jdk8"))
    testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.21")
    implementation("javax.annotation:javax.annotation-api:1.3.2")
    implementation("io.grpc:grpc-kotlin-stub:1.3.0")
    implementation("io.grpc:grpc-protobuf:1.47.0")
    implementation("com.google.protobuf:protobuf-kotlin:3.21.1")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
    implementation("org.hyperledger.fabric:fabric-gateway-java:2.2.5")
    implementation("ch.qos.logback:logback-classic:1.2.11")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.3.1")
    runtimeOnly("io.grpc:grpc-netty:${rootProject.ext["grpcVersion"]}")
}

tasks.test {
    useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "11"
}

tasks.withType<KotlinCompile>().all {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn")
    }
}


application {
    mainClass.set("MainKt")
}

task("runServer", JavaExec::class) {
    main = "grpc.FabricServerKt"
    classpath = sourceSets["main"].runtimeClasspath
}


sourceSets {
    main {
        proto {
            srcDir("proto")
            srcDir("src/main/kotlin/proto")
        }
    }
}

sourceSets {
    val main by getting { }
    main.java.srcDirs("build/generated/source/proto/main/grpc")
    main.java.srcDirs("build/generated/source/proto/main/grpckt")
    main.java.srcDirs("build/generated/source/proto/main/java")
    main.java.srcDirs("build/generated/source/proto/main/kotlin")
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}"
    }
    plugins {
        id("grpc") {
            artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}"
        }
        id("grpckt") {
            artifact = "io.grpc:protoc-gen-grpc-kotlin:${rootProject.ext["grpcKotlinVersion"]}:jdk8@jar"
        }
    }
    generateProtoTasks {
        all().forEach {
            it.plugins {
                id("grpc")
                id("grpckt")
            }
            it.builtins {
                id("kotlin")
            }
        }
    }
}
+14 −0
Original line number Diff line number Diff line
-----BEGIN CERTIFICATE-----
MIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw
cDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH
EwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh
Lm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw
WjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV
BAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT
Y2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3
spCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU
PUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG
AQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD
AgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y
uH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW
-----END CERTIFICATE-----
+73 −0
Original line number Diff line number Diff line
{
    "name": "test-network-org1",
    "version": "1.0.0",
    "client": {
        "organization": "Org1",
        "connection": {
            "timeout": {
                "peer": {
                    "endorser": "300"
                }
            }
        }
    },
    "organizations": {
        "Org1": {
            "mspid": "Org1MSP",
            "peers": [
                "peer0.org1.example.com"
            ],
            "certificateAuthorities": [
                "ca.org1.example.com"
            ]
        }
    },
    "peers": {
        "peer0.org1.example.com": {
            "url": "grpcs://teraflow.nlehd.de:7051",
            "tlsCACerts": {
                "pem": "-----BEGIN CERTIFICATE-----\nMIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw\nWjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV\nBAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT\nY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3\nspCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU\nPUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\nAQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD\nAgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y\nuH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW\n-----END CERTIFICATE-----\n"
            },
            "grpcOptions": {
                "ssl-target-name-override": "peer0.org1.example.com",
                "hostnameOverride": "peer0.org1.example.com"
            }
        },
        "peer0.org2.example.com": {
            "url": "grpcs://teraflow.nlehd.de:9051",
            "tlsCACerts": {
                "pem": "-----BEGIN CERTIFICATE-----\nMIICHjCCAcWgAwIBAgIUL48scgv9ItATkBjSNhzYDjLUDsAwCgYIKoZIzj0EAwIw\nbDELMAkGA1UEBhMCVUsxEjAQBgNVBAgTCUhhbXBzaGlyZTEQMA4GA1UEBxMHSHVy\nc2xleTEZMBcGA1UEChMQb3JnMi5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eub3Jn\nMi5leGFtcGxlLmNvbTAeFw0yMjA5MjcwODMwMDBaFw0zNzA5MjMwODMwMDBaMGwx\nCzAJBgNVBAYTAlVLMRIwEAYDVQQIEwlIYW1wc2hpcmUxEDAOBgNVBAcTB0h1cnNs\nZXkxGTAXBgNVBAoTEG9yZzIuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2NhLm9yZzIu\nZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ5qz8FfrEQ5S08\nr/avPyTrF2grXj5L4DnbvF4YEZ5Usnbm8Svovu7PO8uiVcwT5vrt6ssOdpBFZYu3\nNndpojnYo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd\nBgNVHQ4EFgQUYcp7axYV9AaIptYQqhiCL0VDmXQwCgYIKoZIzj0EAwIDRwAwRAIg\nWT1V8/6flUPNcBkmbtEEKf83k7+6sR9k1a2wtVeJFnQCIE0ZSIL3k0dKQydQBpiz\nPcZZUULvQivcMlIsw5+mjIGc\n-----END CERTIFICATE-----\n"
            },
            "grpcOptions": {
                "ssl-target-name-override": "peer0.org2.example.com",
                "hostnameOverride": "peer0.org2.example.com"
            }
        }
    },
    "certificateAuthorities": {
        "ca.org1.example.com": {
            "url": "https://teraflow.nlehd.de:7054",
            "caName": "ca-org1",
            "tlsCACerts": {
                "pem": [
                    "-----BEGIN CERTIFICATE-----\nMIICJzCCAc2gAwIBAgIUb5gDMfVeVdQjFkK3uC8LtlogN+gwCgYIKoZIzj0EAwIw\ncDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQH\nEwZEdXJoYW0xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh\nLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIwOTI3MDgzMDAwWhcNMzcwOTIzMDgzMDAw\nWjBwMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExDzANBgNV\nBAcTBkR1cmhhbTEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMT\nY2Eub3JnMS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDC3\nspCTT3pjfFXxkX/SFuBgWRiceR8rSoCNQOnIPeNGZK8xl2Zr7VuY06gqy9c+ecSU\nPUWaXiCQxiLgZuS6TOWjRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG\nAQH/AgEBMB0GA1UdDgQWBBRFWSc7GZqcJJyJjXSEspzgAYInGzAKBggqhkjOPQQD\nAgNIADBFAiEAodqc+adkiMuU6iv1IF8uJ/nMQbvMGoP3pb2827QzDosCICOw6W+y\nuH03H3RO6KhOcS1ZzPjspyjrcC+dwzYX4DpW\n-----END CERTIFICATE-----\n"
                ]
            },
            "httpOptions": {
                "verify": false
            }
        }
    },
    "orderers": {
        "orderer0.example.com": {
            "url": "grpcs://teraflow.nlehd.de:7050",
            "tlsCACerts": {
                "pem": "-----BEGIN CERTIFICATE-----\nMIICCzCCAbGgAwIBAgIUdZQo3q4OqyxIkidmAV4QkewCylIwCgYIKoZIzj0EAwIw\nYjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcg\nWW9yazEUMBIGA1UEChMLZXhhbXBsZS5jb20xFzAVBgNVBAMTDmNhLmV4YW1wbGUu\nY29tMB4XDTIyMDkyNzA4MzAwMFoXDTM3MDkyMzA4MzAwMFowYjELMAkGA1UEBhMC\nVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQHEwhOZXcgWW9yazEUMBIGA1UE\nChMLZXhhbXBsZS5jb20xFzAVBgNVBAMTDmNhLmV4YW1wbGUuY29tMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAERR0UzsHSFoyON+9Noxmk1IhnTvSdLWGgEpEwrqVr\n5DwitkeJwRWq134JBTmXuZzsUG87oN6Hr94XAEe4j9Zq8qNFMEMwDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFN8XsELp/X0akrlJ\nY3/BWo2jZS3cMAoGCCqGSM49BAMCA0gAMEUCIQCZYYXW/0h3Kq4BmROpOHfrondg\nopf5LndeujYlH3i8tQIgCtpTQiDXZd+IAUduRmn7a46CwJSbjYbXFVX5vumIbE4=\n-----END CERTIFICATE-----\n"
            },
            "grpcOptions": {
                "ssl-target-name-override": "orderer0.example.com",
                "hostnameOverride": "orderer0.example.com"
            }
        }
    }
}
Loading