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