/*
* ============================================================================
* GNU General Public License
* ============================================================================
*
* Copyright (C) 2006-2011 Serotonin Software Technologies Inc. http://serotoninsoftware.com
* @author Matthew Lohbihler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* When signing a commercial license with Serotonin Software Technologies Inc.,
* the following extension to GPL is made. A special exception to the GPL is
* included to allow you to distribute a combined work that includes BAcnet4J
* without being obliged to provide the source code for any proprietary components.
*/
package com.serotonin.bacnet4j.apdu;
import com.serotonin.bacnet4j.enums.MaxApduLength;
import com.serotonin.bacnet4j.enums.MaxSegments;
import com.serotonin.bacnet4j.exception.BACnetException;
import com.serotonin.bacnet4j.service.confirmed.ConfirmedRequestService;
import com.serotonin.bacnet4j.type.constructed.ServicesSupported;
import org.free.bacnet4j.util.ByteQueue;
public class ConfirmedRequest extends APDU implements Segmentable {
private static final long serialVersionUID = -8338535273752015450L;
public static final byte TYPE_ID = 0;
public static int getHeaderSize(boolean segmented) {
if (segmented)
return 6;
return 4;
}
/**
* This parameter indicates whether or not the confirmed service request is entirely, or only partially, contained
* in the present PDU. If the request is present in its entirety, the value of the 'segmented-message' parameter
* shall be FALSE. If the present PDU contains only a segment of the request, this parameter shall be TRUE.
*/
private boolean segmentedMessage;
/**
* This parameter is only meaningful if the 'segmented-message' parameter is TRUE. If 'segmented-message' is TRUE,
* then the 'more-follows' parameter shall be TRUE for all segments comprising the confirmed service request except
* for the last and shall be FALSE for the final segment. If 'segmented-message' is FALSE, then 'more-follows' shall
* be set FALSE by the encoder and shall be ignored by the decoder.
*/
private boolean moreFollows;
/**
* This parameter shall be TRUE if the device issuing the confirmed request will accept a segmented complex
* acknowledgment as a response. It shall be FALSE otherwise. This parameter is included in the confirmed request so
* that the responding device may determine how to convey its response.
*/
private boolean segmentedResponseAccepted;
/**
* This optional parameter specifies the maximum number of segments that the device will accept. This parameter is
* included in the confirmed request so that the responding device may determine how to convey its response. The
* parameter shall be encoded as follows: B'000' Unspecified number of segments accepted. B'001' 2 segments
* accepted. B'010' 4 segments accepted. B'011' 8 segments accepted. B'100' 16 segments accepted. B'101' 32 segments
* accepted. B'110' 64 segments accepted. B'111' Greater than 64 segments accepted.
*/
private MaxSegments maxSegmentsAccepted;
/**
* This parameter specifies the maximum size of a single APDU that the issuing device will accept. This parameter is
* included in the confirmed request so that the responding device may determine how to convey its response. The
* parameter shall be encoded as follows: B'0000' Up to MinimumMessageSize (50 octets) B'0001' Up to 128 octets
* B'0010' Up to 206 octets (fits in a LonTalk frame) B'0011' Up to 480 octets (fits in an ARCNET frame) B'0100' Up
* to 1024 octets B'0101' Up to 1476 octets (fits in an ISO 8802-3 frame) B'0110' reserved by ASHRAE B'0111'
* reserved by ASHRAE B'1000' reserved by ASHRAE B'1001' reserved by ASHRAE B'1010' reserved by ASHRAE B'1011'
* reserved by ASHRAE B'1100' reserved by ASHRAE B'1101' reserved by ASHRAE B'1110' reserved by ASHRAE B'1111'
* reserved by ASHRAE
*/
private MaxApduLength maxApduLengthAccepted;
/**
* This parameter shall be an integer in the range 0 - 255 assigned by the service requester. It shall be used to
* associate the response to a confirmed service request with the original request. In the absence of any error, the
* 'invokeID' shall be returned by the service provider in a BACnet-SimpleACK-PDU or a BACnet-ComplexACK-PDU. In the
* event of an error condition, the 'invokeID' shall be returned by the service provider in a BACnet-Error-PDU,
* BACnet-Reject-PDU, or BACnet-Abort-PDU as appropriate.
*
* The 'invokeID' shall be generated by the device issuing the service request. It shall be unique for all
* outstanding confirmed request APDUs generated by the device. The same 'invokeID' shall be used for all segments
* of a segmented service request. Once an 'invokeID' has been assigned to an APDU, it shall be maintained within
* the device until either a response APDU is received with the same 'invokeID' or a no response timer expires (see
* 5.3). In either case, the 'invokeID' value shall then be released for reassignment. The algorithm used to pick a
* value out of the set of unused values is a local matter. The storage mechanism for maintaining the used
* 'invokeID' values within the requesting and responding devices is also a local matter. The requesting device may
* use a single 'invokeID' space for all its confirmed APDUs or multiple 'invokeID' spaces (one per destination
* device address) as desired. Since the 'invokeID' values are only source-device-unique, the responding device
* shall maintain the 'invokeID' as well as the requesting device address until a response has been sent. The
* responding device may discard the 'invokeID' information after a response has been sent.
*/
private byte invokeId;
/**
* This optional parameter is only present if the 'segmented-message' parameter is TRUE. In this case, the
* 'sequence-number' shall be a sequentially incremented unsigned integer, modulo 256, which identifies each segment
* of a segmented request. The value of the received 'sequence-number' is used by the responder to acknowledge the
* receipt of one or more segments of a segmented request. The 'sequence-number' of the first segment of a segmented
* request shall be zero.
*/
private int sequenceNumber;
/**
* This optional parameter is only present if the 'segmented-message' parameter is TRUE. In this case, the
* 'proposed-windowsize' parameter shall specify as an unsigned binary integer the maximum number of message
* segments containing 'invokeID' the sender is able or willing to send before waiting for a segment acknowledgment
* PDU (see 5.2 and 5.3). The value of the 'proposed-window-size' shall be in the range 1 - 127.
*/
private int proposedWindowSize;
/**
* This parameter shall contain the parameters of the specific service that is being requested, encoded according to
* the rules of 20.2. These parameters are defined in the individual service descriptions in this standard and are
* represented in Clause 21 in accordance with the rules of ASN.1.
*/
private byte serviceChoice;
private ConfirmedRequestService serviceRequest;
/**
* This field is used to allow parsing of only the APDU so that those fields are available in case there is a
* problem parsing the service request.
*/
private ByteQueue serviceData;
public ConfirmedRequest(boolean segmentedMessage, boolean moreFollows, boolean segmentedResponseAccepted,
MaxSegments maxSegmentsAccepted, MaxApduLength maxApduLengthAccepted, byte invokeId, int sequenceNumber,
int proposedWindowSize, ConfirmedRequestService serviceRequest) {
setFields(segmentedMessage, moreFollows, segmentedResponseAccepted, maxSegmentsAccepted, maxApduLengthAccepted,
invokeId, sequenceNumber, proposedWindowSize, serviceRequest.getChoiceId());
this.serviceRequest = serviceRequest;
}
public ConfirmedRequest(boolean segmentedMessage, boolean moreFollows, boolean segmentedResponseAccepted,
MaxSegments maxSegmentsAccepted, MaxApduLength maxApduLengthAccepted, byte invokeId, int sequenceNumber,
int proposedWindowSize, byte serviceChoice, ByteQueue serviceData) {
setFields(segmentedMessage, moreFollows, segmentedResponseAccepted, maxSegmentsAccepted, maxApduLengthAccepted,
invokeId, sequenceNumber, proposedWindowSize, serviceChoice);
this.serviceData = serviceData;
}
private void setFields(boolean segmentedMessage, boolean moreFollows, boolean segmentedResponseAccepted,
MaxSegments maxSegmentsAccepted, MaxApduLength maxApduLengthAccepted, byte invokeId, int sequenceNumber,
int proposedWindowSize, byte serviceChoice) {
this.segmentedMessage = segmentedMessage;
this.moreFollows = moreFollows;
this.segmentedResponseAccepted = segmentedResponseAccepted;
this.maxSegmentsAccepted = maxSegmentsAccepted;
this.maxApduLengthAccepted = maxApduLengthAccepted;
this.invokeId = invokeId;
this.sequenceNumber = sequenceNumber;
this.proposedWindowSize = proposedWindowSize;
this.serviceChoice = serviceChoice;
}
@Override
public byte getPduType() {
return TYPE_ID;
}
@Override
public byte getInvokeId() {
return invokeId;
}
@Override
public int getSequenceNumber() {
return sequenceNumber;
}
public MaxApduLength getMaxApduLengthAccepted() {
return maxApduLengthAccepted;
}
public MaxSegments getMaxSegmentsAccepted() {
return maxSegmentsAccepted;
}
@Override
public boolean isMoreFollows() {
return moreFollows;
}
@Override
public int getProposedWindowSize() {
return proposedWindowSize;
}
@Override
public boolean isSegmentedMessage() {
return segmentedMessage;
}
public boolean isSegmentedResponseAccepted() {
return segmentedResponseAccepted;
}
public ConfirmedRequestService getServiceRequest() {
return serviceRequest;
}
@Override
public void appendServiceData(ByteQueue data) {
this.serviceData.push(data);
}
@Override
public ByteQueue getServiceData() {
return serviceData;
}
@Override
public void write(ByteQueue queue) {
queue.push(getShiftedTypeId(TYPE_ID) | (segmentedMessage ? 8 : 0) | (moreFollows ? 4 : 0)
| (segmentedResponseAccepted ? 2 : 0));
queue.push(((maxSegmentsAccepted.getId() & 7) << 4) | (maxApduLengthAccepted.getId() & 0xf));
queue.push(invokeId);
if (segmentedMessage) {
queue.push(sequenceNumber);
queue.push(proposedWindowSize);
}
queue.push(serviceChoice);
if (serviceRequest != null)
serviceRequest.write(queue);
else
queue.push(serviceData);
}
ConfirmedRequest(ServicesSupported servicesSupported, ByteQueue queue) throws BACnetException {
byte b = queue.pop();
segmentedMessage = (b & 8) != 0;
moreFollows = (b & 4) != 0;
segmentedResponseAccepted = (b & 2) != 0;
b = queue.pop();
maxSegmentsAccepted = MaxSegments.valueOf((byte) ((b & 0x70) >> 4));
maxApduLengthAccepted = MaxApduLength.valueOf((byte) (b & 0xf));
invokeId = queue.pop();
if (segmentedMessage) {
sequenceNumber = queue.popU1B();
proposedWindowSize = queue.popU1B();
}
serviceChoice = queue.pop();
serviceData = new ByteQueue(queue.popAll());
ConfirmedRequestService.checkConfirmedRequestService(servicesSupported, serviceChoice);
}
@Override
public void parseServiceData() throws BACnetException {
if (serviceData != null) {
serviceRequest = ConfirmedRequestService.createConfirmedRequestService(serviceChoice, serviceData);
serviceData = null;
}
}
@Override
public APDU clone(boolean moreFollows, int sequenceNumber, int actualSegWindow, ByteQueue serviceData) {
return new ConfirmedRequest(this.segmentedMessage, moreFollows, this.segmentedResponseAccepted,
this.maxSegmentsAccepted, this.maxApduLengthAccepted, this.invokeId, sequenceNumber, actualSegWindow,
this.serviceChoice, serviceData);
}
@Override
public String toString() {
return "ConfirmedRequest(segmentedMessage=" + segmentedMessage + ", moreFollows=" + moreFollows
+ ", segmentedResponseAccepted=" + segmentedResponseAccepted + ", maxSegmentsAccepted="
+ maxSegmentsAccepted + ", maxApduLengthAccepted=" + maxApduLengthAccepted + ", invokeId=" + invokeId
+ ", sequenceNumber=" + sequenceNumber + ", proposedWindowSize=" + proposedWindowSize
+ ", serviceChoice=" + serviceChoice + ", serviceRequest=" + serviceRequest + ")";
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + invokeId;
result = PRIME * result + ((maxApduLengthAccepted == null) ? 0 : maxApduLengthAccepted.hashCode());
result = PRIME * result + ((maxSegmentsAccepted == null) ? 0 : maxSegmentsAccepted.hashCode());
result = PRIME * result + (moreFollows ? 1231 : 1237);
result = PRIME * result + proposedWindowSize;
result = PRIME * result + (segmentedMessage ? 1231 : 1237);
result = PRIME * result + (segmentedResponseAccepted ? 1231 : 1237);
result = PRIME * result + sequenceNumber;
result = PRIME * result + serviceChoice;
result = PRIME * result + ((serviceData == null) ? 0 : serviceData.hashCode());
result = PRIME * result + ((serviceRequest == null) ? 0 : serviceRequest.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ConfirmedRequest other = (ConfirmedRequest) obj;
if (invokeId != other.invokeId)
return false;
if (maxApduLengthAccepted == null) {
if (other.maxApduLengthAccepted != null)
return false;
}
else if (!maxApduLengthAccepted.equals(other.maxApduLengthAccepted))
return false;
if (maxSegmentsAccepted != other.maxSegmentsAccepted)
return false;
if (moreFollows != other.moreFollows)
return false;
if (proposedWindowSize != other.proposedWindowSize)
return false;
if (segmentedMessage != other.segmentedMessage)
return false;
if (segmentedResponseAccepted != other.segmentedResponseAccepted)
return false;
if (sequenceNumber != other.sequenceNumber)
return false;
if (serviceChoice != other.serviceChoice)
return false;
if (serviceData == null) {
if (other.serviceData != null)
return false;
}
else if (!serviceData.equals(other.serviceData))
return false;
if (serviceRequest == null) {
if (other.serviceRequest != null)
return false;
}
else if (!serviceRequest.equals(other.serviceRequest))
return false;
return true;
}
@Override
public boolean expectsReply() {
return true;
}
}