com.offbynull.portmapper.pcp.PcpResponse.java Source code

Java tutorial

Introduction

Here is the source code for com.offbynull.portmapper.pcp.PcpResponse.java

Source

/*
 * Copyright (c) 2013-2014, Kasra Faghihi, All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.offbynull.portmapper.pcp;

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.Validate;

/**
 * Represents a PCP response. Provides PCP request header construction functionality. From the RFC:
 * <pre>
 * 7.2.  Response Header
 * 
 *    All responses have the following format:
 * 
 *       0                   1                   2                   3
 *       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *      |  Version = 2  |R|   Opcode    |   Reserved    |  Result Code  |
 *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *      |                      Lifetime (32 bits)                       |
 *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *      |                     Epoch Time (32 bits)                      |
 *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *      |                                                               |
 *      |                      Reserved (96 bits)                       |
 *      |                                                               |
 *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *      :                                                               :
 *      :             (optional) Opcode-specific response data          :
 *      :                                                               :
 *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *      :             (optional) Options                                :
 *      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * 
 *                   Figure 3: Common Response Packet Format
 * 
 *    These fields are described below:
 * 
 *    Version:  Responses from servers compliant with this specification
 *       MUST use version 2.  This is set by the server.
 * 
 *    R: Indicates Request (0) or Response (1).  All Responses MUST use 1.
 *       This is set by the server.
 * 
 *    Opcode:  The 7-bit Opcode value.  The server copies this value from
 *       the request.
 * 
 *    Reserved:  8 reserved bits, MUST be sent as 0, MUST be ignored when
 *       received.  This is set by the server.
 * 
 *    Result Code:  The result code for this response.  See Section 7.4 for
 *       values.  This is set by the server.
 * 
 *    Lifetime:  An unsigned 32-bit integer, in seconds, ranging from 0 to
 *       2^32-1 seconds.  On an error response, this indicates how long
 *       clients should assume they'll get the same error response from
 *       that PCP server if they repeat the same request.  On a success
 *       response for the PCP Opcodes that create a mapping (MAP and PEER),
 *       the Lifetime field indicates the lifetime for this mapping.  This
 *       is set by the server.
 * 
 *    Epoch Time:  The server's Epoch Time value.  See Section 8.5 for
 *       discussion.  This value is set by the server, in both success and
 *       error responses.
 * 
 *    Reserved:  96 reserved bits.  For requests that were successfully
 *       parsed, this MUST be sent as 0, MUST be ignored when received.
 *       This is set by the server.  For requests that were not
 *       successfully parsed, the server copies the last 96 bits of the PCP
 *       Client's IP Address field from the request message into this
 *       corresponding 96-bit field of the response.
 * 
 *    Opcode-specific information:  Payload data for this Opcode.  The
 *       length of this data is determined by the Opcode definition.
 * 
 *    PCP Options:  Zero, one, or more options that are legal for both a
 *       PCP response and for this Opcode.  See Section 7.3.
 * </pre>
 * @author Kasra Faghihi
 */
public abstract class PcpResponse {
    private int op;
    private long lifetime;
    private long epochTime;
    private List<PcpOption> options;

    /**
     * Constructs a {@link PcpResponse} object by parsing a buffer.
     * @param buffer buffer containing PCP response data
     * @throws NullPointerException if any argument is {@code null}
     * @throws BufferUnderflowException if not enough data is available in {@code buffer}
     * @throws IllegalArgumentException if the version doesn't match the expected version (must always be {@code 2}), or if the r-flag isn't
     * set, or if there's an unsuccessful/unrecognized result code
     */
    PcpResponse(ByteBuffer buffer) {
        Validate.notNull(buffer);

        if (buffer.remaining() < 4 || buffer.remaining() > 1100 || buffer.remaining() % 4 != 0) {
            throw new IllegalArgumentException("Bad packet size: " + buffer.remaining());
        }

        int version = buffer.get() & 0xFF;
        Validate.isTrue(version == 2, "Unknown version: %d", version);

        int temp = buffer.get() & 0xFF;
        Validate.isTrue((temp & 128) == 128, "Bad R-flag: %d", temp);
        op = temp & 0x7F; // discard first bit, it was used for rflag

        buffer.get(); // skip reserved field

        int resultCodeNum = buffer.get() & 0xFF;
        PcpResultCode[] resultCodes = PcpResultCode.values();

        Validate.isTrue(resultCodeNum < resultCodes.length, "Unknown result code encountered: %d", resultCodeNum);
        Validate.isTrue(resultCodeNum == PcpResultCode.SUCCESS.ordinal(), "Unsuccessful result code: %s [%s]",
                resultCodes[resultCodeNum].toString(), resultCodes[resultCodeNum].getMessage());

        lifetime = buffer.getInt() & 0xFFFFFFFFL;

        epochTime = buffer.getInt() & 0xFFFFFFFFL;

        for (int i = 0; i < 12; i++) {
            buffer.get();
            //Validate.isTrue(buffer.get() == 0, "Reserved space indicates unsuccessful response");
        }

        options = Collections.emptyList();
    }

    /**
     * Get opcode.
     * @return opcode
     */
    public final int getOp() {
        return op;
    }

    /**
     * Get lifetime.
     * @return lifetime in seconds
     */
    public final long getLifetime() {
        return lifetime;
    }

    /**
     * Get epoch time.
     * @return epoch time in seconds
     */
    public final long getEpochTime() {
        return epochTime;
    }

    /**
     * Get PCP options.
     * @return PCP options (unmodifiable)
     */
    public final List<PcpOption> getOptions() {
        return options;
    }

    /**
     * MUST be called by child class constructor so that PCP options can be parsed.
     * @param buffer buffer containing PCP response data, with the pointer at the point which PCP options begin
     * @throws NullPointerException if any argument is {@code null}
     * @throws BufferUnderflowException if not enough data is available in {@code buffer}
     */
    protected final void parseOptions(ByteBuffer buffer) {
        Validate.notNull(buffer);

        List<PcpOption> pcpOptionsList = new ArrayList<>();
        while (buffer.hasRemaining()) {
            PcpOption option;

            try {
                buffer.mark();
                option = new FilterPcpOption(buffer);
                pcpOptionsList.add(option);
                continue;
            } catch (BufferUnderflowException | IllegalArgumentException e) {
                buffer.reset();
            }

            try {
                buffer.mark();
                option = new PreferFailurePcpOption(buffer);
                pcpOptionsList.add(option);
                continue;
            } catch (BufferUnderflowException | IllegalArgumentException e) {
                buffer.reset();
            }

            try {
                buffer.mark();
                option = new ThirdPartyPcpOption(buffer);
                pcpOptionsList.add(option);
                continue;
            } catch (BufferUnderflowException | IllegalArgumentException e) {
                buffer.reset();
            }

            option = new UnknownPcpOption(buffer);
            pcpOptionsList.add(option);
        }

        options = Collections.unmodifiableList(pcpOptionsList);
    }
}