/* * Copyright (c) 2003 University of Murcia. All rights reserved. * -------------------------------------------------------------- * For more information, please see . */ package org.umu.cops.stack; import org.umu.cops.stack.COPSHeader.Flag; import org.umu.cops.stack.COPSHeader.OPCode; import org.umu.cops.stack.COPSObjHeader.CType; import java.io.IOException; import java.io.OutputStream; import java.net.Socket; import java.util.*; /** * COPS Decision Message (RFC 2748 page. 23) * * The PDP responds to the REQ with a DEC message that includes the * associated client handle and one or more decision objects grouped * relative to a Context object and Decision Flags object type pair. If * there was a protocol error an error object is returned instead. * * It is required that the first decision message for a new/updated * request will have the solicited message flag set (value = 1) in the * COPS header. This avoids the issue of keeping track of which updated * request (that is, a request reissued for the same handle) a * particular decision corresponds. It is important that, for a given * handle, there be at most one outstanding solicited decision per * request. This essentially means that the PEP SHOULD NOT issue more * than one REQ (for a given handle) before it receives a corresponding * DEC with the solicited message flag set. The PDP MUST always issue * decisions for requests on a particular handle in the order they * arrive and all requests MUST have a corresponding decision. * * To avoid deadlock, the PEP can always timeout after issuing a request * that does not receive a decision. It MUST then delete the timed-out * handle, and may try again using a new handle. * * The format of the Decision message is as follows: * * ::= * * | * [] * * ::= | * * ::= * * [] * [] * [] * [] * * The Decision message may include either an Error object or one or * more context plus associated decision objects. COPS protocol problems * are reported in the Error object (e.g. an error with the format of * the original request including malformed request messages, unknown * COPS objects in the Request, etc.). The applicable Decision object(s) * depend on the context and the type of client. The only ordering * requirement for decision objects is that the required Decision Flags * object type MUST precede the other Decision object types per context * binding. */ public class COPSDecisionMsg extends COPSMsg { // Required private final COPSHandle _clientHandle; // Optional private final COPSError _error; private final Map> _decisions; private final COPSIntegrity _integrity; private final COPSClientSI _decSI; /** * Constructor for Decision messages containing a COPS Error. * As this has been deprecated, the constructor containing the version and Flag should be used going forward. * @param clientType - the client type (required) * @param clientHandle - the handle (required) * @param error - the error (required) * @param integrity - the integrity (optional) * @param decSI - the client SI for the description(optional) */ @Deprecated public COPSDecisionMsg(final short clientType, final COPSHandle clientHandle, final COPSError error, final COPSIntegrity integrity, final COPSClientSI decSI) { this(new COPSHeader(OPCode.DEC, clientType), clientHandle, error, null, integrity, decSI); } /** * Constructor for Decision messages containing a COPS Error. * @param clientType - the client type (required) * @param clientHandle - the handle (required) * @param error - the error (required) * @param integrity - the integrity (optional) * @param decSI - the client SI for the description(optional) */ public COPSDecisionMsg(final int version, final Flag flag, final short clientType, final COPSHandle clientHandle, final COPSError error, final COPSIntegrity integrity, final COPSClientSI decSI) { this(new COPSHeader(version, flag, OPCode.DEC, clientType), clientHandle, error, null, integrity, decSI); } /** * Constructor for Decision messages containing decisions * As this has been deprecated, the constructor containing the version and Flag should be used going forward. * @param clientType - the client type (required) * @param clientHandle - the handle (required) * @param decisions - the decisions (required) * @param integrity - the integrity (optional) * @param decSI - the client SI for the description(optional) */ @Deprecated public COPSDecisionMsg(final short clientType, final COPSHandle clientHandle, final Map> decisions, final COPSIntegrity integrity, final COPSClientSI decSI) { this(new COPSHeader(OPCode.DEC, clientType), clientHandle, null, decisions, integrity, decSI); } /** * Constructor for Decision messages containing decisions * @param clientType - the client type (required) * @param clientHandle - the handle (required) * @param decisions - the decisions (required) * @param integrity - the integrity (optional) * @param decSI - the client SI for the description(optional) */ public COPSDecisionMsg(final int version, final Flag flag, final short clientType, final COPSHandle clientHandle, final Map> decisions, final COPSIntegrity integrity, final COPSClientSI decSI) { this(new COPSHeader(version, flag, OPCode.DEC, clientType), clientHandle, null, decisions, integrity, decSI); } /** * Constructor generally designed for Decision messages being parsed from a byte array. * @param hdr - the header * @param clientHandle - the handle * @param error - the error (if null, decisions must not be null or empty) * @param decisions - the decisions (must be empty or null if error is not) * @param integrity - the integrity (optional) * @param decSI - the client SI for the description(optional) */ protected COPSDecisionMsg(final COPSHeader hdr, final COPSHandle clientHandle, final COPSError error, final Map> decisions, final COPSIntegrity integrity, final COPSClientSI decSI) { super(hdr); if (!hdr.getOpCode().equals(OPCode.DEC)) throw new IllegalArgumentException("OPCode must be of type - " + OPCode.DEC); if (clientHandle == null) throw new IllegalArgumentException("Client handle must not be null"); if (error == null && (decisions == null || decisions.isEmpty())) throw new IllegalArgumentException("Must contain either an COPSError or at least one decision"); if (error != null && (decisions != null && !decisions.isEmpty())) throw new IllegalArgumentException("Must not contain a COPSError and decisions"); if(decisions == null) _decisions = Collections.unmodifiableMap(new HashMap>()); else _decisions = Collections.unmodifiableMap(decisions); for (Set decSet: _decisions.values()) { if (decSet == null || decSet.isEmpty()) throw new IllegalArgumentException("Decisions are empty"); } _clientHandle = clientHandle; _error = error; _integrity = integrity; _decSI = decSI; } // Getters public COPSHandle getClientHandle() { return _clientHandle; } public COPSError getError() { return _error; } public Map> getDecisions() { return _decisions; } public COPSIntegrity getIntegrity() { return _integrity; } public COPSClientSI getDecSI() { return _decSI; } @Override protected int getDataLength() { int out = 0; out += _clientHandle.getDataLength() + _clientHandle.getHeader().getHdrLength(); if (_error != null) out += _error.getDataLength() + _error.getHeader().getHdrLength(); for (final Map.Entry> entry : _decisions.entrySet()) { out += entry.getKey().getDataLength() + entry.getKey().getHeader().getHdrLength(); for (final COPSDecision decision : entry.getValue()) { out += decision.getDataLength() + decision.getHeader().getHdrLength(); } } if (_integrity != null) out += _integrity.getDataLength() + _integrity.getHeader().getHdrLength(); if (_decSI != null) out += _decSI.getDataLength() + _decSI.getHeader().getHdrLength(); return out; } @Override protected void writeBody(final Socket socket) throws IOException { _clientHandle.writeData(socket); if (_error != null) _error.writeData(socket); //Display decisions //Display any local decisions for (final Map.Entry> entry : _decisions.entrySet()) { entry.getKey().writeData(socket); for (final COPSDecision decision : entry.getValue()) { decision.writeData(socket); } } if (_integrity != null) _integrity.writeData(socket); if (_decSI != null) _decSI.writeData(socket); } @Override protected void dumpBody(final OutputStream os) throws IOException { if (_clientHandle != null) _clientHandle.dump(os); if (_error != null) _error.dump(os); //Display any local decisions for (final Map.Entry> entry : _decisions.entrySet()) { entry.getKey().dump(os); for (final COPSDecision decision : entry.getValue()) { decision.dump(os); } } if (_integrity != null) { _integrity.dump(os); } if (_decSI != null) { _decSI.dump(os); } } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (!(o instanceof COPSDecisionMsg)) { return false; } if (!super.equals(o)) { return false; } final COPSDecisionMsg that = (COPSDecisionMsg) o; for (final Map.Entry> entry : this._decisions.entrySet()) { final Set thatDecisions = that._decisions.get(entry.getKey()); if (thatDecisions == null) return false; for (final COPSDecision thisDecision : entry.getValue()) { boolean found = false; for (final COPSDecision thatDecision: thatDecisions) { if (thisDecision.equals(thatDecision)) { found = true; break; } } if (! found) return false; } } return _clientHandle.equals(that._clientHandle) && !(_error != null ? !_error.equals(that._error) : that._error != null) && !(_integrity != null ? !_integrity.equals(that._integrity) : that._integrity != null) && !(_decSI != null ? !_decSI.equals(that._decSI) : that._decSI != null); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + _clientHandle.hashCode(); result = 31 * result + (_error != null ? _error.hashCode() : 0); result = 31 * result + _decisions.hashCode(); result = 31 * result + (_integrity != null ? _integrity.hashCode() : 0); result = 31 * result + (_decSI != null ? _decSI.hashCode() : 0); return result; } /** * Responsible for parsing a byte array to create a COPSDecisionMsg object * @param hdrData - the object's header data * @param data - the byte array to parse * @return - the message object * @throws COPSException */ public static COPSDecisionMsg parse(final COPSHeaderData hdrData, final byte[] data) throws COPSException { // Variables for constructor COPSHandle clientHandle = null; COPSContext context = null; COPSError error = null; COPSIntegrity integrity = null; COPSClientSI descSi = null; final Map> decisionMap = new HashMap<>(); int dataStart = 0; while (dataStart < data.length) { final byte[] buf = new byte[data.length - dataStart]; System.arraycopy(data, dataStart, buf, 0, data.length - dataStart); final COPSObjHeaderData objHdrData = COPSObjectParser.parseObjHeader(buf); switch (objHdrData.header.getCNum()) { case HANDLE: clientHandle = COPSHandle.parse(objHdrData, buf); break; case CONTEXT: if (context == null) { context = COPSContext.parse(objHdrData, buf); } else context = COPSContext.parse(objHdrData, buf); break; case ERROR: error = COPSError.parse(objHdrData, buf); break; case DEC: COPSDecision dec; if (objHdrData.header.getCType().equals(CType.CSI)) { // TODO - Revisit, this is pretty darn clunky try { dec = COPSDecision.parse(objHdrData, buf); } catch (IllegalArgumentException e) { descSi = COPSClientSI.parse(objHdrData, buf); dec = null; } } else { dec = COPSDecision.parse(objHdrData, buf); } if (dec != null) { if (decisionMap.get(context) != null) decisionMap.get(context).add(dec); else { final Set decisions = new HashSet<>(); decisions.add(dec); decisionMap.put(context, decisions); } } break; case CSI: descSi = COPSClientSI.parse(objHdrData, buf); break; case MSG_INTEGRITY: integrity = COPSIntegrity.parse(objHdrData, buf); break; default: throw new COPSException("Bad Message format, unknown object type with CNum - " + objHdrData.header.getCNum()); } dataStart += objHdrData.msgByteCount; } return new COPSDecisionMsg(hdrData.header, clientHandle, error, decisionMap, integrity, descSi); } }