1 /*
  2  * Copyright © [2008-2009] Novell, Inc.  All Rights Reserved.
  3  * 
  4  * USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE DEVELOPER LICENSE AGREEMENT
  5  * OR OTHER AGREEMENT THROUGH WHICH NOVELL, INC. MAKES THE WORK AVAILABLE.  THIS WORK 
  6  * MAY NOT BE ADAPTED WITHOUT NOVELL'S PRIOR WRITTEN CONSENT.
  7  * 
  8  * NOVELL PROVIDES THE WORK "AS IS," WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, 
  9  * INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR 
 10  * A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  NOVELL, THE AUTHORS OF THE WORK, AND THE 
 11  * OWNERS OF COPYRIGHT IN THE WORK ARE NOT LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER 
 12  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, 
 13  * OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS IN THE WORK.
 14  */
 15 
 16 /**
 17  * @fileoverview
 18  * This file defines the Session class, which is a set of stored input records that are
 19  * kept for later processing.
 20  * @name Session Class
 21  */
 22 	
 23 	/**
 24  * Constructs a new event Session to be used to store partial Records which will later be 
 25  * combined into an Event.
 26  * @class
 27  * The Session object is used for temporary record storage when an event source does
 28  * not provide complete information in a single record. The basic concept is that a
 29  * storage area is created where multiple "sessions" can be stored - each session is made
 30  * up of one or more records, and has a key that is used to access that session.
 31  * Each session uses a key, which should uniquely identify the session and allow
 32  * new events to be matched against and added to that session. Often related events will 
 33  * have a grouping ID or similar concept, and you might want to combine that with an IP
 34  * address or other source identifier to prevent overlaps.
 35  * <p>
 36  * In most cases, you will gather up a set of records, storing each one in the associated
 37  * session, until a given session has enough information to generate a full event.
 38  * It is possible to explicitly fetch a session and process it, if for example you are using
 39  * it to store referential data you will add to multiple events, but more typically you will
 40  * have something indirectly trigger the processing of a session. There are three triggers:
 41  * <ol>
 42  * <li>Record count: you can set a threshold for the number of records that should be stored
 43  *    in the session (this is set per-session, so you can base it on the type of session)
 44  *    being stored).
 45  * <li>Wallclock timer: The session can be set to persist for a set number of wall-clock seconds,
 46  *    and then be processed. A method is used to attach a wallTimer.
 47  * <li>Event timer: The session can be set to persist for a set number of pseudo-seconds,
 48  *    which are calculated based on the perceived time according to the received events,
 49  *    and then be processed. A method is used to attach an eventTimer.
 50  * </ol>
 51  * Note that for the Event timer, each event source might have its own idea of time, so you
 52  * should definitely ensure that when this is used the session key includes an event source
 53  * ID like an IP address. Also, "time" will not advance unless a new event record with a
 54  * later time is received from that source, so there are cases where sessions will never
 55  * process if data stops flowing. You may want to include a longer Wallclock timer to protect
 56  * against this case.
 57  * <p> 
 58  * Note that the default Collector template automatically creates an empty storage area
 59  * called instance.STORE - the normal Session methods will store sessions in this area.
 60  * The key must be specified but all other arguments are optional.
 61  * <p>
 62  * Finally, in order to parse a Session the preferred method is to let it expire or overflow (as above),
 63  * the template will then call a pre-define parsing method on the Session that you've attached via
 64  * the addParser() method (in other words, the Session will parse itself). Within the Session, your
 65  * parsing code should use the retrieve() method to fetch the array of stored records, but note that record
 66  * number 0 is special: it's empty by default, and all your partial results should be stored there. When you
 67  * send() the Session, record 0 will be used as the source for mapping data into the output event.
 68  * Example:
 69  * <pre>
 70  * // in instance.initialize():
 71  * instance.PARSER.audit = function() {
 72  *		var newEvt = new Event(instance.CONFIG.protoEvt);
 73  *		var sessRecs = this.retrieve();
 74  *		
 75  *		switch (sessRecs[sessRecs.length-1].hdrMap.type)
 76  *		{
 77  *			case "SYSCALL":
 78  *				sessRecs[0].parsesyscall(sessRecs,newEvt);
 79  *				this.send(newEvt);
 80  *				break;
 81  *			case "CONFIG_CHANGE":
 82  *        sessRecs[0].parseconfig(sessRecs,newEvt);
 83  *				break;
 84  *			default:
 85  *				sessRecs[0].sendUnsupported();
 86  *				return false;
 87  *				break;
 88  *		}
 89  *	}
 90  * // in Record.prototype.parse():
 91  *  this.evtgrpid = this.s_RXBufferString.substr(10,25);
 92  *  var sessionKey = this.s_RV24 + ":" + this.evtgrpid;
 93  *	var sess = Session.get(sessionKey.trim());
 94  *	if ( !sess ) {
 95  *		sess = new Session(sessionKey);
 96  *		sess.addWallTimer(instance.CONFIG.params.Session_Timeout);
 97  *		sess.addParser(instance.PARSER.audit);
 98  *	}
 99  *	sess.store(this);
100  * </pre>
101  * <p>Note that:
102  * <ul>
103  * <li>In the session parser (instance.PARSER.something), "this" will be the Session object.</li>
104  * <li>A new event is created so that you can use Event.add2EI and Event.setDeviceEventTime and so forth.</li>
105  * <li>In the parsing routines, sessRecs (or the variable you declare) is the array of records returned by retrieve() from the session.</li>
106  * <li>The first record in that set is the record that should be used to contain all of your partial results.</li>
107  * <li>The records from index 1 thru sessRecs.length-1, therefore, represent the records you stored in the session.</li>
108  * <li>You must pass your newly created Event to the Session.send() method for proper operation.</li>
109  * </ul>
110  * @param {String} key  - The key used to uniquely identify this session
111  * @param {Number} maxRecs  - maximum number of records to be stored in this session
112  */
113 function Session(key, maxRecs) {
114   
115 	/**
116 	 * An array to hold the records stored in the Session.
117 	 * NOTE: The first Record is actually a blank record which will be used to hold
118 	 * your parsing results when you parse the Session. Records you add to the session
119 	 * will start at index 1.
120 	 * @type [Record]
121 	 */
122 	this.Records = [];
123 	this.Records[0] = new Record();
124 	
125 	/**
126 	 * The key used to reference the Session.
127 	 * @type String
128 	 */
129 	this.Key = key;
130 	
131 	/**
132 	 * The parser used to process the Session once it expires.
133 	 */
134 	this.Parser = function() { return true; };
135 	
136 	/**
137 	 * The maximum number of records this Session can hold.
138 	 * Set this to automatically trigger processing when this number of records is reached.
139 	 * @type Number
140 	 */
141 	this.MaxRecs = (maxRecs) ? (maxRecs+1) : (1000000);
142 	
143 	instance.STORE[key] = this;
144 	
145 	/** 
146 	 * Session.getKey() returns the key for this session.
147 	 * @return {String} The Session key
148 	 */
149 	this.getKey = function() {
150 		return this.Key;
151 	};
152 	
153 	/**
154 	 * Adds a partial record as part of the session.
155 	 * <p>Example:
156 	 * <pre>
157 	 * // in Record.parse() routine...
158 	 * if ( this.partial ) {
159 	 *   sess.store(this);
160 	 *   return false;
161 	 * }
162 	 * </pre>
163 	 * @param {Record} rec  The Record object to store in the session.
164 	 * @return {Boolean} Result
165 	 */
166 	this.store = function(rec) {
167 		this.Records.push(rec);
168 		return true;
169 	};
170 	
171 	/**
172 	 * Returns an array of the stored records in this session.
173 	 * Usually this is done by the parsing routines attached to sessions.
174 	 * NOTE: The first Record is actually a blank record which will be used to hold
175 	 * your parsing results when you parse the Session. Records you add to the session
176 	 * will start at index 1.
177 	 * <p>
178 	 * Example:
179 	 * <pre>
180 	 * instance.PARSER.parseSession = function() {
181 	 *   partialRecs = sess.retrieve();
182 	 *   // parse data from partialRecs into partialRecs[0]
183 	 *   var sessEvt = new Event(instance.protoEvt);
184 	 *   this.send(sessEvt);
185 	 *   return true;
186 	 * }
187 	 * </pre>
188 	 * @return {Record[]} Records in the Session
189 	 */
190 	this.retrieve = function() {
191 		return this.Records;
192 	};
193 	
194 	/**
195 	 * Empties out a session and sets it to null.
196 	 * @return {Boolean} Result
197 	 */
198 	this.clear = function() {
199 		instance.STORE[this.Key] = null;
200 		delete instance.STORE[this.Key];
201 	};
202 	
203 	/**
204 	 * Describes a session as a string.
205 	 * @return {String} Description of session
206 	 */
207 	this.toString = function() {
208 		return "Session: key = " + this.getKey() + "; # of records = " + this.Records.length;
209 	};
210 	
211 	/**
212 	 * Adds a wallclock-based timer to this session. Pass in the number
213 	 * of seconds to wait before the session expires; this will be added to the current time
214 	 * to set a expiration time for this session.
215  	 * @param {Number} expiry  # of seconds before this session expires
216  	 * @return {Boolean} Result
217  	 */
218 	this.addWallTimer = function( expiry ) {
219 	  this.WallTimer = new Date().getTime() + (expiry * 1000);
220 	};
221 		
222 	/**
223 	 * Adds an event-based timer to the session.
224 	 * <p>
225 	 * <strong>For now, implemented as a wall timer. This is experimental and will change.</strong>
226 	 * @param {Number} currentTime  The current time according to the event source
227 	 * @param {Number} expiry  Number of seconds for this session to persist (according to event-based 
228    * time starting with the first event in the session)
229    * @return {Boolean} Result
230 	 */
231 	this.addEvtTimer = function( expiry, srcParser, timeParser ) {
232 		 this.EvtTimer = new Date().getTime() + (expiry * 1000);
233 	};
234 		
235 	/**
236 	 * Adds a parser that will be automatically called when the session expires.
237 	 * The parser should be defined as a function that can be attached to the session.
238 	 * instance.PARSER is provided as a convenient place to store pre-defined parser routines.
239 	 * <p>
240 	 * Example:
241 	 * <pre>
242 	 * // ...in initialize() method
243 	 * instance.PARSER.login = function() {
244 	 *   rec.username = recs[0].s_RXBufferString.substr(12,36);
245 	 *   ...
246 	 * }
247 	 * // ..in parse() method after a new session has been created
248 	 * sess.addParser(instance.PARSER.login);
249 	 * </pre>
250 	 * @param {Function} parser  - The parser function to attach to this session
251 	 * @return {Boolean} Result
252 	 */
253 	this.addParser = function( parser ) {
254 		if ( typeof parser != 'function' ) { return false; }
255 		this.Parser = parser;
256 		return true;
257 	};
258 	
259 	/**
260 	 * Sends this Session as an event to Sentinel.
261 	 * Note that before you send a Session, you must (in the parsing method attached to the session):
262 	 * 1) Create a new, empty event
263 	 * 2) Parse the data in stored records (indices 1 thru n), storing results
264 	 * into the record at index 0.
265 	 * 3) Set the deviceEventTime, taxonomy key, and any extended information on the new event
266 	 * 4) Call this method, passing in the new event.
267 	 * @param {Object} event  The event which will be sent to Sentinel.
268 	 */
269 	this.send = function( event ) {
270 		if ( ! event instanceof Event ) {
271 			return false;
272 		}
273 		this.Records[0].connectorData = this.Records[this.Records.length-1].connectorData;
274 		rec = this.Records[0];
275 		event.send();
276 	};
277 }
278 
279 /**
280  * Returns a session, if any exists, associated with a given key.
281  * @param {String} key  The key used to refer to the Session
282  * @return {Session} The Session associated with the key
283  */
284 Session.get = function(key) {
285 	return instance.STORE[key];
286 };
287