Merge "Changed tests to leverage a dynamic port for testing COPS message marshalling...
[packetcable.git] / packetcable-driver / src / main / java / org / umu / cops / stack / COPSDecision.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.COPSObjHeader.CNum;
10 import org.umu.cops.stack.COPSObjHeader.CType;
11
12 import java.io.IOException;
13 import java.io.OutputStream;
14 import java.net.Socket;
15 import java.util.Map;
16 import java.util.concurrent.ConcurrentHashMap;
17
18 /**
19  * COPS Decision (RFC 2748)
20  *
21  * Decision made by the PDP. Appears in replies. The specific non-
22  * mandatory decision objects required in a decision to a particular
23  * request depend on the type of client.
24  *
25  * C-Num = 6
26  * C-Type = 1, Decision Flags (Mandatory)
27  *
28  * Commands:
29  * 0 = NULL Decision (No configuration data available)
30  * 1 = Install (Admit request/Install configuration)
31  * 2 = Remove (Remove request/Remove configuration)
32  *
33  * Flags:
34  * 0x01 = Trigger Error (Trigger error message if set)
35  * Note: Trigger Error is applicable to client-types that
36  * are capable of sending error notifications for signaled
37  * messages.
38  *
39  * Flag values not applicable to a given context's R-Type or
40  * client-type MUST be ignored by the PEP.
41  *
42  * C-Type = 2, Stateless Data
43  *
44  * This type of decision object carries additional stateless
45  * information that can be applied by the PEP locally. It is a
46  * variable length object and its internal format SHOULD be
47  * specified in the relevant COPS extension document for the given
48  * client-type. This object is optional in Decision messages and is
49  * interpreted relative to a given context.
50  *
51  * It is expected that even outsourcing PEPs will be able to make
52  * some simple stateless policy decisions locally in their LPDP. As
53  * this set is well known and implemented ubiquitously, PDPs are
54  * aware of it as well (either universally, through configuration,
55  * or using the Client-Open message). The PDP may also include this
56  * information in its decision, and the PEP MUST apply it to the
57  * resource allocation event that generated the request.
58  *
59  * C-Type = 3, Replacement Data
60  *
61  * This type of decision object carries replacement data that is to
62  * replace existing data in a signaled message. It is a variable
63  * length object and its internal format SHOULD be specified in the
64  * relevant COPS extension document for the given client-type. It is
65  * optional in Decision messages and is interpreted relative to a
66  * given context.
67  *
68  * C-Type = 4, Client Specific Decision Data
69  *
70  * Additional decision types can be introduced using the Client
71  * Specific Decision Data Object. It is a variable length object and
72  * its internal format SHOULD be specified in the relevant COPS
73  * extension document for the given client-type. It is optional in
74  * Decision messages and is interpreted relative to a given context.
75  *
76  * C-Type = 5, Named Decision Data
77  *
78  * Named configuration information is encapsulated in this version
79  * of the decision object in response to configuration requests. It
80  * is a variable length object and its internal format SHOULD be
81  * specified in the relevant COPS extension document for the given
82  * client-type. It is optional in Decision messages and is
83  * interpreted relative to both a given context and decision flags.
84  */
85 public class COPSDecision extends COPSObjBase {
86
87     static Map<Integer, Command> VAL_TO_CMD = new ConcurrentHashMap<>();
88     static {
89         VAL_TO_CMD.put(Command.NULL.ordinal(), Command.NULL);
90         VAL_TO_CMD.put(Command.INSTALL.ordinal(), Command.INSTALL);
91         VAL_TO_CMD.put(Command.REMOVE.ordinal(), Command.REMOVE);
92     }
93
94     static Map<Integer, DecisionFlag> VAL_TO_FLAG = new ConcurrentHashMap<>();
95     static {
96         VAL_TO_FLAG.put(DecisionFlag.NA.ordinal(), DecisionFlag.NA);
97         VAL_TO_FLAG.put(DecisionFlag.REQERROR.ordinal(), DecisionFlag.REQERROR);
98         VAL_TO_FLAG.put(DecisionFlag.REQSTATE.ordinal(), DecisionFlag.REQSTATE);
99     }
100
101     /**
102      * All CTypes are supported except NA
103      */
104     private final Command _cmdCode;
105     private final DecisionFlag _flags;
106     private final COPSData _data;
107     private final COPSData _padding;
108
109     /**
110      * Constructor generally used for sending messages without any extra data
111      * @param cmdCode - the command
112      * @throws java.lang.IllegalArgumentException
113      */
114     public COPSDecision(final Command cmdCode) {
115         this(CType.DEF, cmdCode, DecisionFlag.NA, new COPSData());
116     }
117
118     /**
119      * Constructor generally used for sending messages with a specific CType and extra data and a NA decision flag
120      * @param cType - the CType
121      * @param data - the data
122      * @throws java.lang.IllegalArgumentException
123      */
124     public COPSDecision(final CType cType, final COPSData data) {
125         this(cType, Command.NULL, DecisionFlag.NA, data);
126     }
127
128     /**
129      * Constructor generally used for sending messages with a specific Command and DecisionFlag
130      * @param cmdCode - the command
131      * @param flags - the flags
132      * @throws java.lang.IllegalArgumentException
133      */
134     public COPSDecision(final Command cmdCode, final DecisionFlag flags) {
135         this(CType.DEF, cmdCode, flags, new COPSData());
136     }
137
138     /**
139      * Constructor generally used for sending messages with a specific, CType, Command and DecisionFlag
140      * @param cType - the CType
141      * @param cmdCode - the command
142      * @param flags - the flags
143      * @throws java.lang.IllegalArgumentException
144      */
145     public COPSDecision(final CType cType, final Command cmdCode, final DecisionFlag flags) {
146         this(cType, cmdCode, flags, new COPSData());
147     }
148
149     /**
150      * Constructor generally used for sending messages with a specific, CType, Command, DecisionFlag and data
151      * @param cType - the CType
152      * @param cmdCode - the command
153      * @param flags - the flags
154      * @param data - the data
155      * @throws java.lang.IllegalArgumentException
156      */
157     public COPSDecision(final CType cType, final Command cmdCode, final DecisionFlag flags,
158                         final COPSData data) {
159         this(new COPSObjHeader(CNum.DEC, cType), cmdCode, flags, data);
160     }
161
162     /**
163      * Constructor generally used when parsing the bytes of an inbound COPS message but can also be used when the
164      * COPSObjHeader information is known
165      * @param hdr - the object header
166      * @param cmdCode - the command
167      * @param flags - the flags
168      * @param data - the data
169      * @throws java.lang.IllegalArgumentException
170      */
171     protected COPSDecision(final COPSObjHeader hdr, final Command cmdCode, final DecisionFlag flags,
172                            final COPSData data) {
173         super(hdr);
174         // TODO - find a better way to make this check
175         if (this.getClass().getName().equals("org.umu.cops.stack.COPSDecision") && !hdr.getCNum().equals(CNum.DEC))
176             throw new IllegalArgumentException("CNum must be equal to " + CNum.DEC);
177
178         if (hdr.getCType().equals(CType.NA)) throw new IllegalArgumentException("CType must not be " + CType.NA);
179         if (cmdCode == null) throw new IllegalArgumentException("Command code must not be null");
180         if (flags == null) throw new IllegalArgumentException("Flags must not be null");
181         if (data == null) throw new IllegalArgumentException("Data object must not be null");
182
183         _cmdCode = cmdCode;
184         _flags = flags;
185         _data = data;
186
187         if ((_data.length() % 4) != 0) {
188             final int padLen = 4 - (_data.length() % 4);
189             _padding = COPSObjectParser.getPadding(padLen);
190         } else {
191             _padding = new COPSData();
192         }
193     }
194
195     /**
196      * Returns the command
197      * @return - the command
198      */
199     public Command getCommand() { return _cmdCode; }
200
201     @Override
202     public int getDataLength() {
203         return 4 + _data.length() + _padding.length();
204     }
205
206     /**
207      * Get the associated data if decision object is of cType 2 or higher
208      * @return   a COPSData
209      */
210     public COPSData getData() {
211         return (_data);
212     }
213
214     /**
215      * If cType == 1 , get the flags associated
216      * @return   a short
217      */
218     public DecisionFlag getFlag() {
219         return _flags;
220     }
221
222     /**
223      * Method getTypeStr
224      * @return   a String
225      */
226     public String getTypeStr() {
227         switch (this.getHeader().getCType()) {
228             case DEF:
229                 return "Default";
230             case STATELESS:
231                 return "Stateless data";
232             case REPL:
233                 return "Replacement data";
234             case CSI:
235                 return "Client specific decision data";
236             case NAMED:
237                 return "Named decision data";
238             default:
239                 return "Unknown";
240         }
241     }
242
243     @Override
244     protected void writeBody(final Socket socket) throws IOException {
245         final byte[] buf = new byte[4];
246         buf[0] = (byte) (_cmdCode.ordinal() >> 8);
247         buf[1] = (byte) _cmdCode.ordinal();
248         buf[2] = (byte) (_flags.ordinal() >> 8);
249         buf[3] = (byte) _flags.ordinal();
250         COPSUtil.writeData(socket, buf, 4);
251
252         COPSUtil.writeData(socket, _data.getData(), _data.length());
253         if (_padding != null) {
254             COPSUtil.writeData(socket, _padding.getData(), _padding.length());
255         }
256     }
257
258     @Override
259     protected void dumpBody(final OutputStream os) throws IOException {
260         if (this.getHeader().getCType().equals(CType.DEF)) {
261             os.write(("Decision (" + getTypeStr() + ")\n").getBytes());
262             os.write(("Command code: " + _cmdCode + "\n").getBytes());
263             os.write(("Command flags: " + _flags + "\n").getBytes());
264         } else {
265             os.write(("Decision (" + getTypeStr() + ")\n").getBytes());
266             os.write(("Data: " + _data.str() + "\n").getBytes());
267         }
268     }
269
270     @Override
271     public boolean equals(final Object o) {
272         if (this == o) {
273             return true;
274         }
275         if (!(o instanceof COPSDecision)) {
276             return false;
277         }
278         if (!super.equals(o)) {
279             return false;
280         }
281
282         final COPSDecision that = (COPSDecision) o;
283
284         return _cmdCode == that._cmdCode && _flags == that._flags && _data.equals(that._data) &&
285                 _padding.equals(that._padding) ||
286                 COPSUtil.copsDataPaddingEquals(this._data, this._padding, that._data, that._padding);
287     }
288
289     @Override
290     public int hashCode() {
291         int result = super.hashCode();
292         result = 31 * result + _data.hashCode();
293         result = 31 * result + _cmdCode.hashCode();
294         result = 31 * result + _flags.hashCode();
295         result = 31 * result + _padding.hashCode();
296         return result;
297     }
298
299     /**
300      * Parses bytes to return a COPSDecision object
301      * @param objHdrData - the associated header
302      * @param dataPtr - the data to parse
303      * @return - the object
304      * @throws java.lang.IllegalArgumentException
305      */
306     public static COPSDecision parse(final COPSObjHeaderData objHdrData, final byte[] dataPtr) {
307         int _cmdCode = 0;
308         _cmdCode |= ((short) dataPtr[4]) << 8;
309         _cmdCode |= ((short) dataPtr[5]) & 0xFF;
310
311         int _flags = 0;
312         _flags |= ((short) dataPtr[6]) << 8;
313         _flags |= ((short) dataPtr[7]) & 0xFF;
314
315         final COPSData d;
316         if (objHdrData.header.getCType().equals(CType.DEF)) {
317             d = null;
318         } else {
319             d = new COPSData(dataPtr, 8, objHdrData.msgByteCount - 8);
320         }
321         return new COPSDecision(objHdrData.header, COPSDecision.VAL_TO_CMD.get(_cmdCode),
322                 COPSDecision.VAL_TO_FLAG.get(_flags), d);
323     }
324
325     /**
326      * Supported command types
327      */
328     public enum Command {
329         NULL,    // No configuration data available
330         INSTALL, // Admit request/install configuration
331         REMOVE   // Remove request/remove configuration
332     }
333
334     public enum DecisionFlag {
335         NA,
336         REQERROR, // = Trigger error
337         REQSTATE, // = ???
338     }
339
340 }