class NettyIdleStateKeepAliveHandler extends ChannelDuplexHandler {

    private final boolean clientConnection;
    private static final Logger Log = LoggerFactory.getLogger(NettyIdleStateKeepAliveHandler.class);

    public NettyIdleStateKeepAliveHandler(boolean clientConnection) {
        this.clientConnection = clientConnection;
    }

    /**
     * Processes IdleStateEvents triggered by an IdleStateHandler.
     * If the IdleStateEvent is an idle read state, the Netty channel is closed.
     * If the IdleStateEvent is an idle write state, an XMPP ping  request is sent
     * to the remote entity.
     *
     * @param ctx ChannelHandlerContext
     * @param evt Event caught, expect IdleStateEvent
     * @throws Exception when attempting to deliver ping packet
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
            final boolean doPing = ConnectionSettings.Client.KEEP_ALIVE_PING_PROPERTY.getValue() && clientConnection;
            final Channel channel = ctx.channel();
            if (channel.attr(IDLE_FLAG).getAndSet(true) == null) {
                // Idle flag is now set, but wasn't present before.
                if (doPing) {
                    sendPingPacket(channel);
                }
            } else {
                // Idle flag already present. Connection has been idle for a while. Close it.
                final NettyConnection connection = channel.attr(CONNECTION).get();
                Log.debug("Closing connection because of inactivity: {}", connection);
                connection.close(new StreamError(StreamError.Condition.connection_timeout, doPing ? "Connection has been idle and did not respond to a keep-alive check." : "Connection has been idle."), doPing);
            }
        }
        super.userEventTriggered(ctx, evt);
    }

    /**
     * Sends an IQ ping packet on the channel associated with the channel handler context.
     *
     * @param channel Channel over which to send a ping.
     * @throws UnauthorizedException when attempting to deliver ping packet
     */
    private void sendPingPacket(Channel channel) throws UnauthorizedException {<FILL_FUNCTION_BODY>}
}

        NettyConnection connection = channel.attr(CONNECTION).get();
        JID entity = connection.getSession() == null ? null : connection.getSession().getAddress();
        if (entity != null) {
            // Ping the connection to see if it is alive.
            final IQ pingRequest = new IQ(IQ.Type.get);
            pingRequest.setChildElement("ping", IQPingHandler.NAMESPACE);
            pingRequest.setFrom(XMPPServer.getInstance().getServerInfo().getXMPPDomain());
            pingRequest.setTo(entity);

            Log.debug("Pinging connection that has been idle: {}", connection);

            // OF-1497: Ensure that data sent to the client is processed through LocalClientSession, to avoid
            // synchronisation issues with stanza counts related to Stream Management (XEP-0198)!
            LocalClientSession ofSession = (LocalClientSession) SessionManager.getInstance().getSession(entity);
            if (ofSession == null) {
                Log.warn("Trying to ping a Netty connection that's idle, but has no corresponding Openfire session. Netty Connection: {}", connection);
            } else {
                ofSession.deliver(pingRequest);
            }
        }
    
<no_super_class>