nearenough.examples.NettyClient.java Source code

Java tutorial

Introduction

Here is the source code for nearenough.examples.NettyClient.java

Source

/*
 * Copyright (c) 2017 int08h LLC. All rights reserved.
 *
 * int08h LLC licenses Nearenough (the "Software") to you under the Apache License, version 2.0
 * (the "License"); you may not use this Software except in compliance with the License. You may
 * obtain a copy of the License from the LICENSE file included with the Software or 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.
 */

package nearenough.examples;

import static nearenough.util.BytesUtil.hexToBytes;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;

import java.net.InetSocketAddress;
import java.time.Instant;
import nearenough.client.RoughtimeClient;
import nearenough.protocol.RtMessage;
import nearenough.protocol.RtWire;

/**
 * Use Netty to send a request to the given Roughtime server and dump the response (if any)
 */
public final class NettyClient {

    // Hostname and port of the public int08h.com Roughtime server
    private static final String INT08H_SERVER_HOST = "roughtime.int08h.com";
    private static final int INT08H_SERVER_PORT = 2002;

    // Long-term public key of the public int08h.com Roughtime server
    private static final byte[] INT08H_SERVER_PUBKEY = hexToBytes(
            "016e6e0284d24c37c6e4d7d8d5b4e1d3c1949ceaa545bf875616c9dce0c9bec1");

    private static final class RequestHandler extends SimpleChannelInboundHandler<DatagramPacket> {

        private final InetSocketAddress addr;
        private final RoughtimeClient client;

        public RequestHandler(InetSocketAddress addr) {
            this.addr = addr;

            // Creates a new RoughtimeClient.
            // Behind the scenes SecureRandom will be used to generate a unique nonce.
            this.client = new RoughtimeClient(INT08H_SERVER_PUBKEY);
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            // Creates the client request
            RtMessage msg = client.createRequest();

            // Encodes the request for network transmission
            ByteBuf encodedMsg = RtWire.toWire(msg);

            // Sends the request to the Roughtime server
            ctx.writeAndFlush(new DatagramPacket(encodedMsg, addr)).addListener(future -> {
                if (!future.isSuccess()) {
                    System.out.println("Send failed " + future.cause().getMessage());
                }
            });
        }

        @SuppressWarnings("Duplicates")
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) {
            // A reply from the server has been received

            System.out.printf("Read message of %d bytes from %s:\n", msg.content().readableBytes(), msg.sender());

            // Parse the response
            RtMessage response = new RtMessage(msg.content());
            System.out.println(response);

            // Validate the response. Checks that the message is well-formed, all signatures are valid,
            // and our nonce is present in the response.
            client.processResponse(response);

            if (client.isResponseValid()) {
                // Validation passed, the response is good

                // The "midpoint" is the Roughtime server's reported timestamp (in microseconds). And the
                // "radius" is a span of uncertainty around that midpoint. A Roughtime server asserts that
                // its "true time" lies within the span.
                Instant midpoint = Instant.ofEpochMilli(client.midpoint() / 1_000L);
                int radiusSec = client.radius() / 1_000_000;
                System.out.println("midpoint    : " + midpoint + " (radius " + radiusSec + " sec)");

                // For comparison, also print the local clock. If the midpoint and your local time
                // are widely different, check your local machine's time sync!
                Instant local = Instant.now();
                System.out.println("local clock : " + local);

            } else {
                // Validation failed. Print out the reason why.
                System.out.println("Response INVALID: " + client.invalidResponseCause().getMessage());
            }

            ctx.close().addListener(unused -> System.exit(0));
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            ctx.close();

            if (cause instanceof ReadTimeoutException) {
                System.out.println("No reply received from " + addr);
            } else {
                System.out.println("Unexpected exception: " + cause.getMessage());
                throw new RuntimeException(cause);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InetSocketAddress addr = new InetSocketAddress(INT08H_SERVER_HOST, INT08H_SERVER_PORT);

        System.out.printf("Sending request to %s\n", addr);

        // Below is Netty boilerplate for setting-up an event loop and registering a handler
        NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap().group(nioEventLoopGroup).remoteAddress(addr)
                .channel(NioDatagramChannel.class).handler(new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel ch) {
                        ch.pipeline().addLast(new ReadTimeoutHandler(5)).addLast(new RequestHandler(addr));
                    }
                });

        ChannelFuture connectFuture = bootstrap.connect();
        connectFuture.addListener(future -> {
            if (!future.isSuccess()) {
                System.out.println("Connect fail:");
                System.out.println(future.cause().getMessage());
            }
        });

        connectFuture.channel().closeFuture().sync();
        nioEventLoopGroup.shutdownGracefully();
    }
}