public void Test2()
        {
            PrivateKey key = PrivateKey.CreateKey();

            ElasticLayout layout = ElasticLayout.DefineLayout(1024);
            Elastic3D     to     = new Elastic3D((byte)(1 + RndProvider.Next(layout.X)), (byte)(1 + RndProvider.Next(layout.Y)), (byte)(1 + RndProvider.Next(layout.Z)));

            Message message = new Message.Builder()
                              .Body("rndBytes", RndProvider.GetNonZeroBytes(128))
                              .Build(key);

            PrivateKey routerKey = PrivateKey.CreateKey();

            message.RouteTo(to.TimeToLive(), to, layout, routerKey);

            Payload routes = message.Routes();

            Assert.Equal(to.TimeToLive(), routes.Value <byte>("ttl"));
            Assert.Equal(to, routes.Value <Elastic3D>("to"));
            Assert.Equal(layout, routes.Value <ElasticLayout>("layout"));

            Address router = message.Router();

            Assert.Equal(routerKey.Address, router);
            Assert.True(message.VerifyRouter());

            message.TimeToLive(0);

            Assert.Equal(0, message.TimeToLive());

            Assert.Equal(0, new Elastic3D(0, 0, 0).TimeToLive());
            Assert.Equal(1, new Elastic3D(16, 0, 0).TimeToLive());
            Assert.Equal(2, new Elastic3D(13, 15, 0).TimeToLive());
            Assert.Equal(3, new Elastic3D(1, 2, 3).TimeToLive());
        }
        private void OnConsoleCommandPeersLayout(string[] args)
        {
            byte n = Env.Value <byte>("n", 4);

            if (args.Length > 0)
            {
                byte.TryParse(args[0], out n);
            }

            // define layout
            ElasticLayout layout = ElasticLayout.DefineLayout(Peers.Count, n);

            for (byte x = 1; x <= layout.X; x++)
            {
                for (byte y = 1; y <= layout.Y; y++)
                {
                    for (byte z = 1; z <= layout.Z; z++)
                    {
                        Elastic3D coordinates = new Elastic3D(x, y, z);
                        string[]  peers       = Peers.ToArray <string>(coordinates, layout);

                        BConsole.WriteLine(ConsoleColor.DarkCyan, $"Coordinates{coordinates.ToString()}: nPeers={peers.Length}");
                        foreach (var peer in peers)
                        {
                            BConsole.WriteLine(EndPoint.ToString() == peer?ConsoleColor.Yellow : ConsoleColor.Gray, $" {peer}");
                        }
                    }
                }
            }

            BConsole.WriteLine(ConsoleColor.DarkCyan, $"ElasticLayout.DefineLayout({Peers.Count},{n})={layout.ToString()}");
        }
        public void OnConsoleCommandPeersLayout(string[] args)
        {
            if (args.Length == 0)
            {
                BConsole.Warning("n value require! usage: peers.layout(16)");
                return;
            }

            byte n = 0;

            try
            {
                n = Convert.ToByte(args[0]);
            }
            catch (Exception e)
            {
                BConsole.Error($"convert n exception! e={e.Message}");
                return;
            }

            // define layout
            ElasticLayout layout = ElasticLayout.DefineLayout(Peers.Count, n);

            for (byte x = 1; x <= layout.X; x++)
            {
                for (byte y = 1; y <= layout.Y; y++)
                {
                    for (byte z = 1; z <= layout.Z; z++)
                    {
                        Elastic3D coordinates = new Elastic3D(x, y, z);
                        string[]  peers       = Peers.ToArray <string>(coordinates, layout);

                        BConsole.WriteLine(ConsoleColor.DarkCyan, $"Coordinates{coordinates.ToString()}: nPeers={peers.Length}");
                        foreach (var peer in peers)
                        {
                            BConsole.WriteLine($" {peer}");
                        }
                    }
                }
            }

            BConsole.WriteLine(ConsoleColor.DarkCyan, $"ElasticLayout.DefineLayout({Peers.Count},{n})={layout.ToString()}");
        }
        public void PayloadShouldBeParsable()
        {
            Elastic3D     to     = new Elastic3D(1, 2, 3);
            ElasticLayout layout = ElasticLayout.DefineLayout(1024);

            Payload payload = new Payload.Builder()
                              .Value("to", to)
                              .Value("layout", layout)
                              .Build();

            Payload received = Payload.Parse(payload.ToBytes());

            Elastic3D     receivedTo     = received.Value <Elastic3D>("to");
            ElasticLayout receivedLayout = received.Value <ElasticLayout>("layout");

            Assert.Equal(1, receivedTo.X);
            Assert.Equal(2, receivedTo.Y);
            Assert.Equal(3, receivedTo.Z);
            Assert.Equal(to, receivedTo);

            Assert.Equal(layout.X, receivedLayout.X);
            Assert.Equal(layout.Y, receivedLayout.Y);
            Assert.Equal(layout.Z, receivedLayout.Z);
        }
        public void Main()
        {
            byte n         = Env.Value("n", DEFAULT_N);
            int  nTimes    = Env.Value("nTimes", DEFAULT_NTIMES);
            int  msgKBytes = Env.Value("msgKBytes", DEFAULT_MSGKBYTES);

            int nPeers   = Peers.Count;
            int nonce    = 0;
            int waitTime = Math.Min(60000, Math.Max(1000, nPeers * 32));

            new BConsole.MessageBuilder()
            .Append(DarkGreen, $"[{TimeCode}] ")
            .Append(Blue, ">> TEST STARTED!")
            .Append(" n=")
            .Append(DarkGreen, n)
            .Append(", nPeers=")
            .Append(DarkGreen, nPeers)
            .Append(", nTimes=")
            .Append(DarkGreen, nTimes)
            .Append(", msgKBytes=")
            .Append(DarkGreen, msgKBytes)
            .Append(", waitTime=")
            .Append(DarkGreen, waitTime)
            .WriteLine("(ms)");

            // 보고서 초기화
            Reports.Start();

            // 5초에 한번씩 전체 메세지 전송한다.
            Stopwatch sw = Stopwatch.StartNew();

            while (!cts.IsCancellationRequested && nonce++ < nTimes)
            {
                byte[] rndBytes = RndProvider.GetNonZeroBytes(msgKBytes * 1024);

                // message
                Message message = new Message.Builder()
                                  .Action("ping")
                                  .Body("nonce", nonce)
                                  .Body("rndBytes", rndBytes)
                                  .Build(NodeKey);

                ElasticLayout layout = ElasticLayout.DefineLayout(Peers.Count, n);
                Elastic3D     me     = layout.DefineCoordinates(NodeKey.Address);

                // new report item for this message
                TestReports.ReportItem report = Reports.NewItem(message, Peers.Count);

                new BConsole.MessageBuilder()
                .Append(">> Sending message[nonce=")
                .Append(Cyan, nonce)
                .Append("](")
                .Append(DarkCyan, message.ID.Ellipsis())
                .Append("): message.length=")
                .Append(DarkGreen, message.Length)
                .Append(", n=")
                .Append(DarkGreen, n)
                .Append(", layout=")
                .Append(DarkGreen, layout)
                .Append(", host.coordinates=")
                .Append(DarkGreen, me)
                .WriteLine();

                // send message to all peers
                SendAll(message, n);

                // wait for send message complete
                sw.Restart();
                while (!report.ReceivedAll && sw.ElapsedMilliseconds < waitTime * 3 && !cts.IsCancellationRequested)
                {
                    Thread.Sleep(10);
                }

                // message result
                new BConsole.MessageBuilder()
                .Append(">> message[nonce=")
                .Append(Cyan, nonce)
                .Append("](")
                .Append(DarkCyan, message.ID.Ellipsis())
                .Append(") result=")
                .WriteLine(report.ToString());

                // waiting for next message
                if (nonce < nTimes)
                {
                    BConsole.WriteLine(ConsoleColor.White, "Waiting for next message...");
                    while (waitTime - (int)sw.ElapsedMilliseconds > 0 && !cts.IsCancellationRequested)
                    {
                        Thread.Sleep(10);
                    }
                }
            }

            Report();
        }