Repository: mageddo/dns-proxy-server
Pull request #373 (2023-03-18 14:53:40)

TCP Client Fixes
Relates to #370 


Patch:
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 06e08325..1694e9b1 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -1,10 +1,13 @@
-## 3.13.0
+## 3.13.1-snapshot
 * Caching remote solved hostnames for 5 minutes
 * Caching not found hostnames for 1 hour
 * Gui interface to clear the cache
-* Not documented api to get cache size and values
-* Increased remote timeout to 10 seconds to make sure won't get easlily get timeout on remote server slowness
-* Order interfaces by index when choosing an IP as the machine IP, put loopback at the end of the list. 
+* Created but not documented APIs to get cache size its and values
+* Increased remote solver timeout to 10 seconds to make sure won't get easlily get timeout due to server slowness
+* Ordering interfaces by index when choosing an IP as the machine IP, put loopback at the end of the list. 
+* Holding TCP open connections up to 2min respecting RFC-1035 section "4.2.2. TCP usage"
+  * Fixed too many occurrences of "java.net.SocketException: Socket closed"
+* Limited TCP/UDP Server thread pool up to 20 threads due to control memory usage
 
 ## 3.12.1
 * Binding UDP server to anylocalhost just as TCP this way DPS can be used on any interface.
diff --git a/build.gradle b/build.gradle
index ee5dcc79..31875a3a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -36,7 +36,7 @@ dependencies {
   implementation 'org.graalvm.sdk:graal-sdk:22.3.1'
   implementation('jakarta.enterprise:jakarta.enterprise.cdi-api:2.0.2')
   implementation('jakarta.ws.rs:jakarta.ws.rs-api:2.1.6')
-  implementation('com.mageddo.commons:commons-lang:0.1.7')
+  implementation('com.mageddo.commons:commons-lang:0.1.8')
   implementation('org.apache.commons:commons-exec:1.3')
 
   implementation('ch.qos.logback:logback-classic:1.4.5')
diff --git a/gradle.properties b/gradle.properties
index 9e6e9fed..9c9038c5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,1 +1,1 @@
-version=3.13.0-snapshot
+version=3.13.1-snapshot
diff --git a/src/main/java/com/mageddo/commons/lang/Singletons.java b/src/main/java/com/mageddo/commons/lang/Singletons.java
deleted file mode 100644
index 2d8222d5..00000000
--- a/src/main/java/com/mageddo/commons/lang/Singletons.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.mageddo.commons.lang;
-
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Supplier;
-
-public class Singletons {
-
-  private static final Map<String, Object> store = new ConcurrentHashMap<>();
-
-  private Singletons() {
-  }
-
-  public static <T> T createOrGet(Class<?> clazz, Supplier<T> sup) {
-    return createOrGet(clazz.getName(), sup);
-  }
-
-  public static <T> T createOrGet(String key, Supplier<T> sup) {
-    return (T) store.computeIfAbsent(key, _k -> sup.get());
-  }
-// https://stackoverflow.com/questions/15156840/singleton-class-with-several-different-classloaders
-//  public static <T> T createOrGet(String key, Supplier<T> sup) {
-//    // There should be just one system class loader object in the whole JVM.
-//    synchronized (ClassLoader.getSystemClassLoader()) {
-//      Properties sysProps = System.getProperties();
-//      // The key is a String, because the .class object would be different across classloaders.
-//      T cached = (T) sysProps.get(key);
-//
-//      // Some other class loader loaded JvmWideSingleton earlier.
-//      if (cached != null) {
-//        return cached;
-//      } else {
-//        // Otherwise this classloader is the first one, let's create a singleton.
-//        // Make sure not to do any locking within this.
-//        final var hotload = sup.get();
-//        System.getProperties().put(key, hotload);
-//        return hotload;
-//      }
-//    }
-//  }
-
-  public static <T> T get(Class<?> clazz) {
-    return get(clazz.getName());
-  }
-
-  public static <T> T get(String key) {
-    return (T) System
-      .getProperties()
-      .get(key);
-  }
-
-  public static void clear(Class<?> clazz) {
-    clear(clazz.getName());
-  }
-
-  public static void clear(String key) {
-    System.getProperties().remove(key);
-  }
-}
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
index 395830d7..fc9f5345 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/Config.java
@@ -73,9 +73,8 @@ public class Config {
   @NonNull
   private String resolvConfPaths;
 
-  public static SimpleServer.Protocol findDnsServerProtocol() {
-    return SimpleServer.Protocol.BOTH;
-  }
+  @NonNull
+  private SimpleServer.Protocol serverProtocol;
 
   public void resetConfigFile() {
     try {
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java b/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java
index d3c7715f..f5058eb5 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/Configs.java
@@ -8,6 +8,7 @@ import com.mageddo.dnsproxyserver.config.entrypoint.ConfigProps;
 import com.mageddo.dnsproxyserver.config.entrypoint.JsonConfigs;
 import com.mageddo.dnsproxyserver.config.entrypoint.LogLevel;
 import com.mageddo.dnsproxyserver.server.dns.IpAddr;
+import com.mageddo.dnsproxyserver.server.dns.SimpleServer;
 import com.mageddo.dnsproxyserver.utils.Numbers;
 import com.mageddo.utils.Files;
 import com.mageddo.utils.Runtime;
@@ -55,6 +56,9 @@ public class Configs {
       .remoteDnsServers(buildRemoteServers(json.getRemoteDnsServers()))
       .configPath(configPath)
       .resolvConfPaths(env.getResolvConfPath())
+      .serverProtocol(firstNonNullRequiring(
+        json.getServerProtocol(), SimpleServer.Protocol.UDP_TCP
+      ))
       .build();
   }
 
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java
index 277dbad1..e4976eb8 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJson.java
@@ -2,6 +2,7 @@ package com.mageddo.dnsproxyserver.config.entrypoint;
 
 import com.mageddo.dnsproxyserver.config.Config;
 import com.mageddo.dnsproxyserver.server.dns.IpAddr;
+import com.mageddo.dnsproxyserver.server.dns.SimpleServer;
 
 import java.util.List;
 
@@ -32,4 +33,6 @@ public interface ConfigJson {
   List<IpAddr> getRemoteDnsServers();
 
   List<Config.Env> getEnvs();
+
+  SimpleServer.Protocol getServerProtocol();
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java
index 0d809390..1bf044d1 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV1.java
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.mageddo.dnsproxyserver.config.Config;
 import com.mageddo.dnsproxyserver.server.dns.IpAddr;
+import com.mageddo.dnsproxyserver.server.dns.SimpleServer;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.NonNull;
@@ -70,6 +71,11 @@ public class ConfigJsonV1 implements ConfigJson {
     return ConfigJsonV1EnvsConverter.toDomainEnvs(this._envs);
   }
 
+  @Override
+  public SimpleServer.Protocol getServerProtocol() {
+    return null;
+  }
+
   public ConfigJsonV2 toConfigV2() {
     return new ConfigJsonV2()
       .setDomain(this.getDomain())
diff --git a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java
index bdfc12b0..3e5594b3 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/config/entrypoint/ConfigJsonV2.java
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.mageddo.dnsproxyserver.config.Config;
 import com.mageddo.dnsproxyserver.config.Config.Entry.Type;
 import com.mageddo.dnsproxyserver.server.dns.IpAddr;
+import com.mageddo.dnsproxyserver.server.dns.SimpleServer;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
@@ -46,6 +47,8 @@ public class ConfigJsonV2 implements ConfigJson {
 
   private Boolean dpsNetworkAutoConnect;
 
+  private SimpleServer.Protocol serverProtocol;
+
   @JsonIgnore
   public List<IpAddr> getRemoteDnsServers() {
     return this.remoteDnsServers
@@ -60,6 +63,7 @@ public class ConfigJsonV2 implements ConfigJson {
     return ConfigJsonV2EnvsConverter.toDomainEnvs(this._envs);
   }
 
+
   @Data
   @Accessors(chain = true)
   public static class Env {
diff --git a/src/main/java/com/mageddo/dnsproxyserver/dnsconfigurator/DnsConfigurators.java b/src/main/java/com/mageddo/dnsproxyserver/dnsconfigurator/DnsConfigurators.java
index 0b35c924..7fd4b211 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/dnsconfigurator/DnsConfigurators.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/dnsconfigurator/DnsConfigurators.java
@@ -44,7 +44,7 @@ public class DnsConfigurators implements StartupEvent {
 
   private void configurationHook(Config config) {
     ThreadPool
-      .def()
+      .scheduled()
       .scheduleWithFixedDelay(() -> {
         try {
           final var addr = this.findIpAddr();
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandler.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandler.java
index 57d19593..bb45845e 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandler.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandler.java
@@ -40,19 +40,11 @@ class DnsQueryTCPHandler implements SocketClientMessageHandler {
         final var res = this.handler.handle(query, "tcp")
           .toWire();
 
-        try {
-          final var out = client.getOut();
-          out.write(Shorts.toBytes((short) res.length));
-          out.flush();
-          out.write(res);
-          out.flush();
-        } catch (IOException e) {
-          log.info(
-            "status=outIsClosed, query={}, msg={}, class={}",
-            Messages.simplePrint(query), e.getMessage(), ClassUtils.getSimpleName(e)
-          );
-          break;
-        }
+        final var out = client.getOut();
+        out.write(Shorts.toBytes((short) res.length));
+        out.flush();
+        out.write(res);
+        out.flush();
 
         log.debug(
           "status=success, queryMsgSize={}, resMsgSize={}, req={}",
@@ -60,6 +52,11 @@ class DnsQueryTCPHandler implements SocketClientMessageHandler {
         );
 
       }
+    } catch (UncheckedIOException | IOException e) {
+      log.debug(
+        "status=socketClosed, runningTime={}, msg={}, class={}",
+        client.getRunningTime(), e.getMessage(), ClassUtils.getSimpleName(e)
+      );
     } catch (Exception e) {
       log.warn("status=request-failed, msg={}", e.getMessage(), e);
     } finally {
@@ -103,7 +100,7 @@ class DnsQueryTCPHandler implements SocketClientMessageHandler {
       }
       return msgSizeBuf.getShort();
     } catch (IOException e) {
-      throw new RuntimeException(e);
+      throw new UncheckedIOException(e);
     }
   }
 
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java
index ff6bf867..0f65cd48 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/ServerStarter.java
@@ -1,6 +1,5 @@
 package com.mageddo.dnsproxyserver.server.dns;
 
-import com.mageddo.dnsproxyserver.config.Config;
 import com.mageddo.dnsproxyserver.config.Configs;
 import com.mageddo.dnsproxyserver.dnsconfigurator.DpsIpDiscover;
 import com.mageddo.dnsproxyserver.server.dns.solver.Solver;
@@ -30,13 +29,14 @@ public class ServerStarter {
   }
 
   public ServerStarter start() {
-    final var port = Configs.getInstance().getDnsServerPort();
+    final var config = Configs.getInstance();
+    final var port = config.getDnsServerPort();
     this.server.start(
       port,
-      Config.findDnsServerProtocol(),
+      config.getServerProtocol(),
       this.solvers
     );
-    log.trace("status=startingDnsServer, port={}", port);
+    log.debug("status=startingDnsServer, protocol={}, port={}", config.getServerProtocol(), port);
     return this;
   }
 
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java
index 3b9b2724..474a7ce5 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/SimpleServer.java
@@ -46,7 +46,7 @@ public class SimpleServer {
   public enum Protocol {
     UDP,
     TCP,
-    BOTH
+    UDP_TCP
   }
 
 }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/SocketClient.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/SocketClient.java
index f7fe70ae..2f9bb93c 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/SocketClient.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/SocketClient.java
@@ -10,7 +10,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UncheckedIOException;
-import java.net.InetAddress;
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.time.Duration;
@@ -84,7 +83,7 @@ public class SocketClient implements Runnable, AutoCloseable {
         this, stopWatch.getTime(), e.getMessage(), e
       );
     } finally {
-      log.debug("status=finalize-client, client={}, runTime={}", this, stopWatch.getTime());
+      log.debug("status=finalizeClient, client={}, runTime={}", this, stopWatch.getTime());
       this.silentClose();
     }
   }
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/TCPServer.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/TCPServer.java
index ad8f7a02..1f193760 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/TCPServer.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/TCPServer.java
@@ -16,7 +16,7 @@ import java.net.Socket;
 import java.time.Duration;
 import java.util.LinkedHashSet;
 import java.util.Set;
-import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 
 @Slf4j
@@ -24,15 +24,22 @@ import java.util.concurrent.TimeUnit;
 @RequiredArgsConstructor(onConstructor = @__({@Inject}))
 public class TCPServer {
 
-  public static final int MAX_CLIENT_ALIVE_SECS = 5;
-  private final ScheduledExecutorService pool = ThreadPool.create(10);
+  /**
+   * See https://www.ietf.org/rfc/rfc1035.txt section 4.2.2
+   */
+  public static final Duration MAX_CLIENT_ALIVE_DURATION = Duration.ofMinutes(2);
+  public static final int WATCHDOG_DELAY_SECS = 20;
+
+  private final ExecutorService pool = ThreadPool.newCached(20);
   private final Set<WeakReference<SocketClient>> clients = new LinkedHashSet<>();
   private ServerSocket server;
 
   public void start(int port, InetAddress address, SocketClientMessageHandler handler) {
     log.debug("status=tcpServerStartScheduled, port={}", port);
     this.pool.submit(() -> this.start0(port, address, handler));
-    this.pool.scheduleWithFixedDelay(this::watchDog, MAX_CLIENT_ALIVE_SECS, MAX_CLIENT_ALIVE_SECS, TimeUnit.SECONDS);
+    ThreadPool
+      .scheduled()
+      .scheduleWithFixedDelay(this::watchDog, WATCHDOG_DELAY_SECS, WATCHDOG_DELAY_SECS, TimeUnit.SECONDS);
   }
 
   void start0(int port, InetAddress address, SocketClientMessageHandler handler) {
@@ -78,14 +85,14 @@ public class TCPServer {
           } else if (runningForTooLong(client)) {
             client.silentClose();
             itr.remove();
-            log.debug("status=forcedRemove, runningTime={}", client.getRunningTime());
+            log.debug("status=forcedToClose, runningTime={}", client.getRunningTime());
           }
         } finally {
           MDC.clear();
         }
       }
       log.debug(
-          "status=watchdog, removed={}, clientsBefore={}, after={}",
+          "status=watchdog, removed={}, before={}, actual={}",
           clientsBefore - this.clients.size(), clientsBefore, this.clients.size()
       );
     } catch (Throwable e) {
@@ -94,7 +101,7 @@ public class TCPServer {
   }
 
   static boolean runningForTooLong(SocketClient client) {
-    return Duration.ofSeconds(MAX_CLIENT_ALIVE_SECS).compareTo(client.getRunningTime()) <= 0;
+    return MAX_CLIENT_ALIVE_DURATION.compareTo(client.getRunningTime()) <= 0;
   }
 
   public void stop() {
diff --git a/src/main/java/com/mageddo/dnsproxyserver/server/dns/UDPServer.java b/src/main/java/com/mageddo/dnsproxyserver/server/dns/UDPServer.java
index 7154fc0a..a5be9f83 100644
--- a/src/main/java/com/mageddo/dnsproxyserver/server/dns/UDPServer.java
+++ b/src/main/java/com/mageddo/dnsproxyserver/server/dns/UDPServer.java
@@ -23,7 +23,7 @@ public class UDPServer {
   public UDPServer(SocketAddress address, RequestHandler requestHandler) {
     this.address = address;
     this.requestHandler = requestHandler;
-    this.pool = ThreadPool.create();
+    this.pool = ThreadPool.newCached(20);
   }
 
   public void start() {


Test patch:
diff --git a/src/test/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandlerTest.java b/src/test/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandlerTest.java
index 4a79f6bf..a3a586cf 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandlerTest.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/server/dns/DnsQueryTCPHandlerTest.java
@@ -37,7 +37,7 @@ class DnsQueryTCPHandlerTest {
     final var queryOut = new PipedOutputStream(in);
 
     ThreadPool
-      .def()
+      .scheduled()
       .schedule(
         () -> {
 
diff --git a/src/test/java/com/mageddo/dnsproxyserver/templates/ConfigTemplates.java b/src/test/java/com/mageddo/dnsproxyserver/templates/ConfigTemplates.java
index 7a45a64e..7682406b 100644
--- a/src/test/java/com/mageddo/dnsproxyserver/templates/ConfigTemplates.java
+++ b/src/test/java/com/mageddo/dnsproxyserver/templates/ConfigTemplates.java
@@ -3,6 +3,7 @@ package com.mageddo.dnsproxyserver.templates;
 import com.mageddo.dnsproxyserver.config.Config;
 import com.mageddo.dnsproxyserver.config.entrypoint.ConfigEnv;
 import com.mageddo.dnsproxyserver.config.entrypoint.LogLevel;
+import com.mageddo.dnsproxyserver.server.dns.SimpleServer;
 
 import java.nio.file.Paths;
 
@@ -27,7 +28,9 @@ public class ConfigTemplates {
       .dnsServerPort(53)
       .domain("com")
       .logLevel(LogLevel.WARNING)
-      .resolvConfPaths(ConfigEnv.DEFAULT_RESOLV_CONF_PATH);
+      .resolvConfPaths(ConfigEnv.DEFAULT_RESOLV_CONF_PATH)
+      .serverProtocol(SimpleServer.Protocol.UDP_TCP)
+      ;
   }
 
 
diff --git a/src/test/resources/config-json-v1-test/002.json b/src/test/resources/config-json-v1-test/002.json
index c0cf06c7..990a409e 100644
--- a/src/test/resources/config-json-v1-test/002.json
+++ b/src/test/resources/config-json-v1-test/002.json
@@ -22,5 +22,6 @@
   "registerContainerNames" : false,
   "remoteDnsServers" : [ "8.8.8.8", "4.4.4.4" ],
   "version" : 2,
+  "serverProtocol" : null,
   "webServerPort" : 0
 }
diff --git a/src/test/resources/configs-test/001.json b/src/test/resources/configs-test/001.json
index 005c41a0..d4bc5f18 100644
--- a/src/test/resources/configs-test/001.json
+++ b/src/test/resources/configs-test/001.json
@@ -13,5 +13,6 @@
     "port" : 53,
     "rawIP" : "8.8.8.8"
   } ],
+  "serverProtocol" : "UDP_TCP",
   "webServerPort" : 5380
 }
diff --git a/src/test/resources/configs-test/002.json b/src/test/resources/configs-test/002.json
index ba8fe632..a3e9515a 100644
--- a/src/test/resources/configs-test/002.json
+++ b/src/test/resources/configs-test/002.json
@@ -22,5 +22,6 @@
   "registerContainerNames" : null,
   "remoteDnsServers" : [ ],
   "version" : 2,
+  "serverProtocol" : null,
   "webServerPort" : null
 }
diff --git a/src/test/resources/configs-test/004.json b/src/test/resources/configs-test/004.json
index d3241db3..e61ae17e 100644
--- a/src/test/resources/configs-test/004.json
+++ b/src/test/resources/configs-test/004.json
@@ -13,5 +13,6 @@
     "port" : 53,
     "rawIP" : "8.8.8.8"
   } ],
+  "serverProtocol" : "UDP_TCP",
   "webServerPort" : 9393
 }
diff --git a/src/test/resources/json-configs-test/001.json b/src/test/resources/json-configs-test/001.json
index 1a9d51bf..86d13813 100644
--- a/src/test/resources/json-configs-test/001.json
+++ b/src/test/resources/json-configs-test/001.json
@@ -29,5 +29,6 @@
     "4.4.4.4"
   ],
   "version": 2,
+  "serverProtocol" : null,
   "webServerPort": 0
 }