class Ping implements Runnable {

    @Override
    public void run() {
        final int inet, proto;
        if (mDest instanceof Inet6Address) {
            inet = OsConstants.AF_INET6;
            proto = OsConstants.IPPROTO_ICMPV6;
        } else {
            inet = OsConstants.AF_INET;
            proto = OsConstants.IPPROTO_ICMP;
        }
        try {
            final FileDescriptor fd = socket(inet, proto);
            if (fd.valid()) {
                try {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mNetwork != null) {
                        mNetwork.bindSocket(fd);
                    }
                    setLowDelay(fd);

                    final StructPollfd structPollfd = new StructPollfd();
                    structPollfd.fd = fd;
                    structPollfd.events = POLLIN;
                    final StructPollfd[] structPollfds = {structPollfd};
                    for (int i = 0; i < mCount; i++) {
                        final ByteBuffer byteBuffer = mEchoPacketBuilder.build();
                        final byte buffer[] = new byte[byteBuffer.limit()];

                        try {
                            // Note: it appears that the OS updates the Checksum, Identifier, and Sequence number.  The payload appears to be untouched.
                            // These changes are not reflected in the buffer, but in the returning packet.
                            final long start = System.currentTimeMillis();
                            int rc = sendto(fd, byteBuffer);
                            if (rc >= 0) {
                                rc = poll(structPollfds);
                                final long time = calcLatency(start, System.currentTimeMillis());
                                if (rc >= 0) {
                                    if (structPollfd.revents == POLLIN) {
                                        structPollfd.revents = 0;
                                        rc = recvfrom(fd, buffer);
                                        if (rc < 0) {
                                            Log.d(TAG, "recvfrom() return failure: " + rc);
                                        }
                                        mListener.onPing(time, i);
                                    } else {
                                        mListener.onPing(TIMED_OUT_MS, i);
                                    }
                                } else {
                                    mListener.onPingException(new IOException("poll() failed"), i);
                                    break;
                                }
                            } else {
                                mListener.onPingException(new IOException("sendto() failed"), i);
                                break;
                            }
                        } catch (ErrnoException e) {
                            mListener.onPingException(e, i);
                            break;
                        }
                        sleep();
                    }
                } finally {
                    close(fd);
                }
            } else {
                mListener.onPingException(new IOException("Invalid FD " + fd.toString()), 0);
            }
        } catch (ErrnoException | IOException e) {
            mListener.onPingException(e, 0);
        }
    }

}

class TestPing {

    @Test
    public void testErrnoException() {

        final MockPingListener listener = new MockPingListener();
        final ErrnoException fail = new ErrnoException("sendto()", 1);
        new MockPing(null, listener) {
            @Override
            protected int sendto(final FileDescriptor fd, final ByteBuffer byteBuffer) throws ErrnoException {
                throw fail;
            }
        }.run();
        Assert.assertEquals(fail, listener.exception);
    }

}