8e87363e3ae6d4447606e813e376009248a8268d
[packetcable.git] / packetcable-driver / src / main / java / org / umu / cops / stack / COPSDecisionMsg.java
1 /*
2  * Copyright (c) 2003 University of Murcia.  All rights reserved.
3  * --------------------------------------------------------------
4  * For more information, please see <http://www.umu.euro6ix.org/>.
5  */
6
7 package org.umu.cops.stack;
8
9 import org.umu.cops.stack.COPSHeader.Flag;
10 import org.umu.cops.stack.COPSHeader.OPCode;
11 import org.umu.cops.stack.COPSObjHeader.CType;
12
13 import java.io.IOException;
14 import java.io.OutputStream;
15 import java.net.Socket;
16 import java.util.*;
17
18 /**
19  * COPS Decision Message  (RFC 2748 page. 23)
20  *
21  * The PDP responds to the REQ with a DEC message that includes the
22  * associated client handle and one or more decision objects grouped
23  * relative to a Context object and Decision Flags object type pair. If
24  * there was a protocol error an error object is returned instead.
25  *
26  * It is required that the first decision message for a new/updated
27  * request will have the solicited message flag set (value = 1) in the
28  * COPS header. This avoids the issue of keeping track of which updated
29  * request (that is, a request reissued for the same handle) a
30  * particular decision corresponds. It is important that, for a given
31  * handle, there be at most one outstanding solicited decision per
32  * request. This essentially means that the PEP SHOULD NOT issue more
33  * than one REQ (for a given handle) before it receives a corresponding
34  * DEC with the solicited message flag set. The PDP MUST always issue
35  * decisions for requests on a particular handle in the order they
36  * arrive and all requests MUST have a corresponding decision.
37  *
38  * To avoid deadlock, the PEP can always timeout after issuing a request
39  * that does not receive a decision. It MUST then delete the timed-out
40  * handle, and may try again using a new handle.
41  *
42  * The format of the Decision message is as follows:
43  *
44  * <Decision Message> ::= <Common Header>
45  * <Client Handle>
46  * <Decision(s)> | <Error>
47  * [<Integrity>]
48  *
49  * <Decision(s)> ::= <Decision> | <Decision(s)> <Decision>
50  *
51  * <Decision> ::= <Context>
52  * <Decision: Flags>
53  * [<Decision: Stateless Data>]
54  * [<Decision: Replacement Data>]
55  * [<Decision: ClientSI Data>]
56  * [<Decision: Named Data>]
57  *
58  * The Decision message may include either an Error object or one or
59  * more context plus associated decision objects. COPS protocol problems
60  * are reported in the Error object (e.g. an error with the format of
61  * the original request including malformed request messages, unknown
62  * COPS objects in the Request, etc.). The applicable Decision object(s)
63  * depend on the context and the type of client. The only ordering
64  * requirement for decision objects is that the required Decision Flags
65  * object type MUST precede the other Decision object types per context
66  * binding.
67  */
68 public class COPSDecisionMsg extends COPSMsg {
69
70     // Required
71     private final COPSHandle _clientHandle;
72
73     // Optional
74     private final COPSError _error;
75     private final Map<COPSContext, Set<COPSDecision>> _decisions;
76     private final COPSIntegrity _integrity;
77     private final COPSClientSI _decSI;
78
79     /**
80      * Constructor for Decision messages containing a COPS Error.
81      * As this has been deprecated, the constructor containing the version and Flag should be used going forward.
82      * @param clientType - the client type (required)
83      * @param clientHandle - the handle (required)
84      * @param error - the error (required)
85      * @param integrity - the integrity (optional)
86      * @param decSI - the client SI for the description(optional)
87      */
88     @Deprecated
89     public COPSDecisionMsg(final short clientType, final COPSHandle clientHandle, final COPSError error,
90                            final COPSIntegrity integrity, final COPSClientSI decSI) {
91         this(new COPSHeader(OPCode.DEC, clientType), clientHandle, error, null, integrity, decSI);
92     }
93
94     /**
95      * Constructor for Decision messages containing a COPS Error.
96      * @param clientType - the client type (required)
97      * @param clientHandle - the handle (required)
98      * @param error - the error (required)
99      * @param integrity - the integrity (optional)
100      * @param decSI - the client SI for the description(optional)
101      */
102     public COPSDecisionMsg(final int version, final Flag flag, final short clientType, final COPSHandle clientHandle,
103                            final COPSError error, final COPSIntegrity integrity, final COPSClientSI decSI) {
104         this(new COPSHeader(version, flag, OPCode.DEC, clientType), clientHandle, error, null, integrity, decSI);
105     }
106
107     /**
108      * Constructor for Decision messages containing decisions
109      * As this has been deprecated, the constructor containing the version and Flag should be used going forward.
110      * @param clientType - the client type (required)
111      * @param clientHandle - the handle (required)
112      * @param decisions - the decisions (required)
113      * @param integrity - the integrity (optional)
114      * @param decSI - the client SI for the description(optional)
115      */
116     @Deprecated
117     public COPSDecisionMsg(final short clientType, final COPSHandle clientHandle,
118                            final Map<COPSContext, Set<COPSDecision>> decisions, final COPSIntegrity integrity,
119                            final COPSClientSI decSI) {
120         this(new COPSHeader(OPCode.DEC, clientType), clientHandle, null, decisions, integrity, decSI);
121     }
122
123     /**
124      * Constructor for Decision messages containing decisions
125      * @param clientType - the client type (required)
126      * @param clientHandle - the handle (required)
127      * @param decisions - the decisions (required)
128      * @param integrity - the integrity (optional)
129      * @param decSI - the client SI for the description(optional)
130      */
131     public COPSDecisionMsg(final int version, final Flag flag, final short clientType, final COPSHandle clientHandle,
132                            final Map<COPSContext, Set<COPSDecision>> decisions, final COPSIntegrity integrity,
133                            final COPSClientSI decSI) {
134         this(new COPSHeader(version, flag, OPCode.DEC, clientType), clientHandle, null, decisions, integrity, decSI);
135     }
136
137     /**
138      * Constructor generally designed for Decision messages being parsed from a byte array.
139      * @param hdr - the header
140      * @param clientHandle - the handle
141      * @param error - the error (if null, decisions must not be null or empty)
142      * @param decisions - the decisions (must be empty or null if error is not)
143      * @param integrity - the integrity (optional)
144      * @param decSI - the client SI for the description(optional)
145      */
146     protected COPSDecisionMsg(final COPSHeader hdr, final COPSHandle clientHandle,
147                            final COPSError error, final Map<COPSContext, Set<COPSDecision>> decisions,
148                            final COPSIntegrity integrity, final COPSClientSI decSI) {
149         super(hdr);
150         if (!hdr.getOpCode().equals(OPCode.DEC))
151             throw new IllegalArgumentException("OPCode must be of type - " + OPCode.DEC);
152         if (clientHandle == null) throw new IllegalArgumentException("Client handle must not be null");
153         if (error == null && (decisions == null || decisions.isEmpty()))
154             throw new IllegalArgumentException("Must contain either an COPSError or at least one decision");
155         if (error != null && (decisions != null && !decisions.isEmpty()))
156             throw new IllegalArgumentException("Must not contain a COPSError and decisions");
157
158         if(decisions == null) _decisions = Collections.unmodifiableMap(new HashMap<COPSContext, Set<COPSDecision>>());
159         else _decisions = Collections.unmodifiableMap(decisions);
160
161         for (Set<COPSDecision> decSet: _decisions.values()) {
162             if (decSet == null || decSet.isEmpty())
163                 throw new IllegalArgumentException("Decisions are empty");
164         }
165
166         _clientHandle = clientHandle;
167         _error = error;
168         _integrity = integrity;
169         _decSI = decSI;
170     }
171
172     // Getters
173     public COPSHandle getClientHandle() {
174         return _clientHandle;
175     }
176     public COPSError getError() {
177         return _error;
178     }
179     public Map<COPSContext, Set<COPSDecision>> getDecisions() {
180         return _decisions;
181     }
182     public COPSIntegrity getIntegrity() {
183         return _integrity;
184     }
185     public COPSClientSI getDecSI() {
186         return _decSI;
187     }
188
189     @Override
190     protected int getDataLength() {
191         int out = 0;
192         out += _clientHandle.getDataLength() + _clientHandle.getHeader().getHdrLength();
193         if (_error != null) out += _error.getDataLength() + _error.getHeader().getHdrLength();
194
195         for (final Map.Entry<COPSContext, Set<COPSDecision>> entry : _decisions.entrySet()) {
196             out += entry.getKey().getDataLength() + entry.getKey().getHeader().getHdrLength();
197             for (final COPSDecision decision : entry.getValue()) {
198                 out += decision.getDataLength() + decision.getHeader().getHdrLength();
199             }
200         }
201
202         if (_integrity != null) out += _integrity.getDataLength() + _integrity.getHeader().getHdrLength();
203         if (_decSI != null) out += _decSI.getDataLength() + _decSI.getHeader().getHdrLength();
204
205         return out;
206     }
207
208     @Override
209     protected void writeBody(final Socket socket) throws IOException {
210         _clientHandle.writeData(socket);
211         if (_error != null) _error.writeData(socket);
212
213         //Display decisions
214         //Display any local decisions
215         for (final Map.Entry<COPSContext, Set<COPSDecision>> entry : _decisions.entrySet()) {
216             entry.getKey().writeData(socket);
217             for (final COPSDecision decision : entry.getValue()) {
218                 decision.writeData(socket);
219             }
220         }
221
222         if (_integrity != null) _integrity.writeData(socket);
223         if (_decSI != null) _decSI.writeData(socket);
224     }
225
226     @Override
227     protected void dumpBody(final OutputStream os) throws IOException {
228         if (_clientHandle != null)
229             _clientHandle.dump(os);
230         if (_error != null)
231             _error.dump(os);
232
233         //Display any local decisions
234         for (final Map.Entry<COPSContext, Set<COPSDecision>> entry : _decisions.entrySet()) {
235             entry.getKey().dump(os);
236             for (final COPSDecision decision : entry.getValue()) {
237                 decision.dump(os);
238             }
239         }
240         if (_integrity != null) {
241             _integrity.dump(os);
242         }
243         if (_decSI != null) {
244             _decSI.dump(os);
245         }
246     }
247
248     @Override
249     public boolean equals(final Object o) {
250         if (this == o) {
251             return true;
252         }
253         if (!(o instanceof COPSDecisionMsg)) {
254             return false;
255         }
256         if (!super.equals(o)) {
257             return false;
258         }
259
260         final COPSDecisionMsg that = (COPSDecisionMsg) o;
261
262         for (final Map.Entry<COPSContext, Set<COPSDecision>> entry : this._decisions.entrySet()) {
263             final Set<COPSDecision> thatDecisions = that._decisions.get(entry.getKey());
264             if (thatDecisions == null) return false;
265
266             for (final COPSDecision thisDecision : entry.getValue()) {
267                 boolean found = false;
268                 for (final COPSDecision thatDecision: thatDecisions) {
269                     if (thisDecision.equals(thatDecision)) {
270                         found = true;
271                         break;
272                     }
273                 }
274                 if (! found) return false;
275             }
276         }
277
278         return _clientHandle.equals(that._clientHandle) &&
279                 !(_error != null ? !_error.equals(that._error) : that._error != null) &&
280                 !(_integrity != null ? !_integrity.equals(that._integrity) : that._integrity != null) &&
281                 !(_decSI != null ? !_decSI.equals(that._decSI) : that._decSI != null);
282
283     }
284
285     @Override
286     public int hashCode() {
287         int result = super.hashCode();
288         result = 31 * result + _clientHandle.hashCode();
289         result = 31 * result + (_error != null ? _error.hashCode() : 0);
290         result = 31 * result + _decisions.hashCode();
291         result = 31 * result + (_integrity != null ? _integrity.hashCode() : 0);
292         result = 31 * result + (_decSI != null ? _decSI.hashCode() : 0);
293         return result;
294     }
295
296     /**
297      * Responsible for parsing a byte array to create a COPSDecisionMsg object
298      * @param hdrData - the object's header data
299      * @param data - the byte array to parse
300      * @return - the message object
301      * @throws COPSException
302      */
303     public static COPSDecisionMsg parse(final COPSHeaderData hdrData, final byte[] data) throws COPSException {
304         // Variables for constructor
305         COPSHandle clientHandle = null;
306         COPSContext context = null;
307         COPSError error = null;
308         COPSIntegrity integrity = null;
309         COPSClientSI descSi = null;
310         final Map<COPSContext, Set<COPSDecision>> decisionMap = new HashMap<>();
311
312         int dataStart = 0;
313         while (dataStart < data.length) {
314             final byte[] buf = new byte[data.length - dataStart];
315             System.arraycopy(data, dataStart, buf, 0, data.length - dataStart);
316
317             final COPSObjHeaderData objHdrData = COPSObjectParser.parseObjHeader(buf);
318             switch (objHdrData.header.getCNum()) {
319                 case HANDLE:
320                     clientHandle = COPSHandle.parse(objHdrData, buf);
321                     break;
322                 case CONTEXT:
323                     if (context == null) {
324                         context = COPSContext.parse(objHdrData, buf);
325                     } else context = COPSContext.parse(objHdrData, buf);
326                     break;
327                 case ERROR:
328                     error = COPSError.parse(objHdrData, buf);
329                     break;
330                 case DEC:
331                     COPSDecision dec;
332                     if (objHdrData.header.getCType().equals(CType.CSI)) {
333                         // TODO - Revisit, this is pretty darn clunky
334                         try {
335                             dec = COPSDecision.parse(objHdrData, buf);
336                         } catch (IllegalArgumentException e) {
337                             descSi = COPSClientSI.parse(objHdrData, buf);
338                             dec = null;
339                         }
340                     } else {
341                         dec = COPSDecision.parse(objHdrData, buf);
342                     }
343                     if (dec != null) {
344                         if (decisionMap.get(context) != null)
345                             decisionMap.get(context).add(dec);
346                         else {
347                             final Set<COPSDecision> decisions = new HashSet<>();
348                             decisions.add(dec);
349                             decisionMap.put(context, decisions);
350                         }
351                     }
352                     break;
353                 case CSI:
354                     descSi = COPSClientSI.parse(objHdrData, buf);
355                     break;
356                 case MSG_INTEGRITY:
357                     integrity = COPSIntegrity.parse(objHdrData, buf);
358                     break;
359                 default:
360                     throw new COPSException("Bad Message format, unknown object type with CNum - "
361                             + objHdrData.header.getCNum());
362             }
363             dataStart += objHdrData.msgByteCount;
364         }
365
366         return new COPSDecisionMsg(hdrData.header, clientHandle, error, decisionMap, integrity, descSi);
367     }
368
369 }