1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview
7 * A class of server log entries.
8 */
9
10'use strict';
11
12/** @suppress {duplicate} */
13var remoting = remoting || {};
14
15/**
16 * @private
17 * @constructor
18 */
19remoting.ServerLogEntry = function() {
20  /** @type Object.<string, string> */ this.dict = {};
21};
22
23/** @private */
24remoting.ServerLogEntry.KEY_EVENT_NAME_ = 'event-name';
25/** @private */
26remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_ =
27    'session-state';
28
29/** @private */
30remoting.ServerLogEntry.KEY_SESSION_ID_ = 'session-id';
31
32/** @private */
33remoting.ServerLogEntry.KEY_ROLE_ = 'role';
34/** @private */
35remoting.ServerLogEntry.VALUE_ROLE_CLIENT_ = 'client';
36
37/** @private */
38remoting.ServerLogEntry.KEY_SESSION_STATE_ = 'session-state';
39
40/**
41 * @private
42 * @param {remoting.ClientSession.State} state
43 * @return {string}
44 */
45remoting.ServerLogEntry.getValueForSessionState = function(state) {
46  switch(state) {
47    case remoting.ClientSession.State.UNKNOWN:
48      return 'unknown';
49    case remoting.ClientSession.State.CREATED:
50      return 'created';
51    case remoting.ClientSession.State.CONNECTING:
52      return 'connecting';
53    case remoting.ClientSession.State.INITIALIZING:
54      return 'initializing';
55    case remoting.ClientSession.State.CONNECTED:
56      return 'connected';
57    case remoting.ClientSession.State.CLOSED:
58      return 'closed';
59    case remoting.ClientSession.State.FAILED:
60      return 'connection-failed';
61    case remoting.ClientSession.State.CONNECTION_DROPPED:
62      return 'connection-dropped';
63    case remoting.ClientSession.State.CONNECTION_CANCELED:
64      return 'connection-canceled';
65    default:
66      return 'undefined-' + state;
67  }
68};
69
70/** @private */
71remoting.ServerLogEntry.KEY_CONNECTION_ERROR_ = 'connection-error';
72
73/**
74 * @private
75 * @param {remoting.Error} connectionError
76 * @return {string}
77 */
78remoting.ServerLogEntry.getValueForError =
79    function(connectionError) {
80  switch(connectionError) {
81    case remoting.Error.NONE:
82      return 'none';
83    case remoting.Error.INVALID_ACCESS_CODE:
84      return 'invalid-access-code';
85    case remoting.Error.MISSING_PLUGIN:
86      return 'missing_plugin';
87    case remoting.Error.AUTHENTICATION_FAILED:
88      return 'authentication-failed';
89    case remoting.Error.HOST_IS_OFFLINE:
90      return 'host-is-offline';
91    case remoting.Error.INCOMPATIBLE_PROTOCOL:
92      return 'incompatible-protocol';
93    case remoting.Error.BAD_PLUGIN_VERSION:
94      return 'bad-plugin-version';
95    case remoting.Error.NETWORK_FAILURE:
96      return 'network-failure';
97    case remoting.Error.HOST_OVERLOAD:
98      return 'host-overload';
99    case remoting.Error.P2P_FAILURE:
100      return 'p2p-failure';
101    case remoting.Error.UNEXPECTED:
102      return 'unexpected';
103    default:
104      return 'unknown-' + connectionError;
105  }
106};
107
108/** @private */
109remoting.ServerLogEntry.KEY_SESSION_DURATION_ = 'session-duration';
110
111/** @private */
112remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_ =
113    "connection-statistics";
114/** @private */
115remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_ = "video-bandwidth";
116/** @private */
117remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_ = "capture-latency";
118/** @private */
119remoting.ServerLogEntry.KEY_ENCODE_LATENCY_ = "encode-latency";
120/** @private */
121remoting.ServerLogEntry.KEY_DECODE_LATENCY_ = "decode-latency";
122/** @private */
123remoting.ServerLogEntry.KEY_RENDER_LATENCY_ = "render-latency";
124/** @private */
125remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_ = "roundtrip-latency";
126
127/** @private */
128remoting.ServerLogEntry.KEY_OS_NAME_ = 'os-name';
129/** @private */
130remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_ = 'Windows';
131/** @private */
132remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_ = 'Linux';
133/** @private */
134remoting.ServerLogEntry.VALUE_OS_NAME_MAC_ = 'Mac';
135/** @private */
136remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_ = 'ChromeOS';
137
138/** @private */
139remoting.ServerLogEntry.KEY_OS_VERSION_ = 'os-version';
140
141/** @private */
142remoting.ServerLogEntry.KEY_CPU_ = 'cpu';
143
144/** @private */
145remoting.ServerLogEntry.KEY_BROWSER_VERSION_ = 'browser-version';
146
147/** @private */
148remoting.ServerLogEntry.KEY_WEBAPP_VERSION_ = 'webapp-version';
149
150/** @private */
151remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_OLD_ = 'session-id-old';
152
153/** @private */
154remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_NEW_ = 'session-id-new';
155
156/** @private */
157remoting.ServerLogEntry.KEY_MODE_ = 'mode';
158/** @private */
159remoting.ServerLogEntry.VALUE_MODE_IT2ME_ = 'it2me';
160/** @private */
161remoting.ServerLogEntry.VALUE_MODE_ME2ME_ = 'me2me';
162/** @private */
163remoting.ServerLogEntry.VALUE_MODE_UNKNOWN_ = 'unknown';
164
165/**
166 * Sets one field in this log entry.
167 *
168 * @private
169 * @param {string} key
170 * @param {string} value
171 */
172remoting.ServerLogEntry.prototype.set = function(key, value) {
173  this.dict[key] = value;
174};
175
176/**
177 * Converts this object into an XML stanza.
178 *
179 * @return {string}
180 */
181remoting.ServerLogEntry.prototype.toStanza = function() {
182  var stanza = '<gr:entry ';
183  for (var key in this.dict) {
184    stanza += escape(key) + '="' + escape(this.dict[key]) + '" ';
185  }
186  stanza += '/>';
187  return stanza;
188};
189
190/**
191 * Prints this object on the debug log.
192 *
193 * @param {number} indentLevel the indentation level
194 */
195remoting.ServerLogEntry.prototype.toDebugLog = function(indentLevel) {
196  /** @type Array.<string> */ var fields = [];
197  for (var key in this.dict) {
198    fields.push(key + ': ' + this.dict[key]);
199  }
200  console.log(Array(indentLevel+1).join("  ") + fields.join(', '));
201};
202
203/**
204 * Makes a log entry for a change of client session state.
205 *
206 * @param {remoting.ClientSession.State} state
207 * @param {remoting.Error} connectionError
208 * @param {remoting.ClientSession.Mode} mode
209 * @return {remoting.ServerLogEntry}
210 */
211remoting.ServerLogEntry.makeClientSessionStateChange = function(state,
212    connectionError, mode) {
213  var entry = new remoting.ServerLogEntry();
214  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
215            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
216  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
217            remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_STATE_);
218  entry.set(remoting.ServerLogEntry.KEY_SESSION_STATE_,
219            remoting.ServerLogEntry.getValueForSessionState(state));
220  if (connectionError != remoting.Error.NONE) {
221    entry.set(remoting.ServerLogEntry.KEY_CONNECTION_ERROR_,
222              remoting.ServerLogEntry.getValueForError(connectionError));
223  }
224  entry.addModeField(mode);
225  return entry;
226};
227
228/**
229 * Adds a session duration to a log entry.
230 *
231 * @param {number} sessionDuration
232 */
233remoting.ServerLogEntry.prototype.addSessionDurationField = function(
234    sessionDuration) {
235  this.set(remoting.ServerLogEntry.KEY_SESSION_DURATION_,
236      sessionDuration.toString());
237};
238
239/**
240 * Makes a log entry for a set of connection statistics.
241 * Returns null if all the statistics were zero.
242 *
243 * @param {remoting.StatsAccumulator} statsAccumulator
244 * @param {remoting.ClientSession.Mode} mode
245 * @return {?remoting.ServerLogEntry}
246 */
247remoting.ServerLogEntry.makeStats = function(statsAccumulator, mode) {
248  var entry = new remoting.ServerLogEntry();
249  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
250            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
251  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
252            remoting.ServerLogEntry.VALUE_EVENT_NAME_CONNECTION_STATISTICS_);
253  entry.addModeField(mode);
254  var nonZero = false;
255  nonZero |= entry.addStatsField(
256      remoting.ServerLogEntry.KEY_VIDEO_BANDWIDTH_,
257      remoting.ClientSession.STATS_KEY_VIDEO_BANDWIDTH, statsAccumulator);
258  nonZero |= entry.addStatsField(
259      remoting.ServerLogEntry.KEY_CAPTURE_LATENCY_,
260      remoting.ClientSession.STATS_KEY_CAPTURE_LATENCY, statsAccumulator);
261  nonZero |= entry.addStatsField(
262      remoting.ServerLogEntry.KEY_ENCODE_LATENCY_,
263      remoting.ClientSession.STATS_KEY_ENCODE_LATENCY, statsAccumulator);
264  nonZero |= entry.addStatsField(
265      remoting.ServerLogEntry.KEY_DECODE_LATENCY_,
266      remoting.ClientSession.STATS_KEY_DECODE_LATENCY, statsAccumulator);
267  nonZero |= entry.addStatsField(
268      remoting.ServerLogEntry.KEY_RENDER_LATENCY_,
269      remoting.ClientSession.STATS_KEY_RENDER_LATENCY, statsAccumulator);
270  nonZero |= entry.addStatsField(
271      remoting.ServerLogEntry.KEY_ROUNDTRIP_LATENCY_,
272      remoting.ClientSession.STATS_KEY_ROUNDTRIP_LATENCY, statsAccumulator);
273  if (nonZero) {
274    return entry;
275  }
276  return null;
277};
278
279/**
280 * Adds one connection statistic to a log entry.
281 *
282 * @private
283 * @param {string} entryKey
284 * @param {string} statsKey
285 * @param {remoting.StatsAccumulator} statsAccumulator
286 * @return {boolean} whether the statistic is non-zero
287 */
288remoting.ServerLogEntry.prototype.addStatsField = function(
289    entryKey, statsKey, statsAccumulator) {
290  var val = statsAccumulator.calcMean(statsKey);
291  this.set(entryKey, val.toFixed(2));
292  return (val != 0);
293};
294
295/**
296 * Makes a log entry for a "this session ID is old" event.
297 *
298 * @param {string} sessionId
299 * @param {remoting.ClientSession.Mode} mode
300 * @return {remoting.ServerLogEntry}
301 */
302remoting.ServerLogEntry.makeSessionIdOld = function(sessionId, mode) {
303  var entry = new remoting.ServerLogEntry();
304  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
305            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
306  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
307            remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_OLD_);
308  entry.addSessionIdField(sessionId);
309  entry.addModeField(mode);
310  return entry;
311};
312
313/**
314 * Makes a log entry for a "this session ID is new" event.
315 *
316 * @param {string} sessionId
317 * @param {remoting.ClientSession.Mode} mode
318 * @return {remoting.ServerLogEntry}
319 */
320remoting.ServerLogEntry.makeSessionIdNew = function(sessionId, mode) {
321  var entry = new remoting.ServerLogEntry();
322  entry.set(remoting.ServerLogEntry.KEY_ROLE_,
323            remoting.ServerLogEntry.VALUE_ROLE_CLIENT_);
324  entry.set(remoting.ServerLogEntry.KEY_EVENT_NAME_,
325            remoting.ServerLogEntry.VALUE_EVENT_NAME_SESSION_ID_NEW_);
326  entry.addSessionIdField(sessionId);
327  entry.addModeField(mode);
328  return entry;
329};
330
331/**
332 * Adds a session ID field to this log entry.
333 *
334 * @param {string} sessionId
335 */
336remoting.ServerLogEntry.prototype.addSessionIdField = function(sessionId) {
337  this.set(remoting.ServerLogEntry.KEY_SESSION_ID_, sessionId);
338}
339
340/**
341 * Adds fields describing the host to this log entry.
342 */
343remoting.ServerLogEntry.prototype.addHostFields = function() {
344  var host = remoting.ServerLogEntry.getHostData();
345  if (host) {
346    if (host.os_name.length > 0) {
347      this.set(remoting.ServerLogEntry.KEY_OS_NAME_, host.os_name);
348    }
349    if (host.os_version.length > 0) {
350      this.set(remoting.ServerLogEntry.KEY_OS_VERSION_, host.os_version);
351    }
352    if (host.cpu.length > 0) {
353      this.set(remoting.ServerLogEntry.KEY_CPU_, host.cpu);
354    }
355  }
356};
357
358/**
359 * Extracts host data from the userAgent string.
360 *
361 * @private
362 * @return {{os_name:string, os_version:string, cpu:string} | null}
363 */
364remoting.ServerLogEntry.getHostData = function() {
365  return remoting.ServerLogEntry.extractHostDataFrom(navigator.userAgent);
366};
367
368/**
369 * Extracts host data from the given userAgent string.
370 *
371 * @private
372 * @param {string} s
373 * @return {{os_name:string, os_version:string, cpu:string} | null}
374 */
375remoting.ServerLogEntry.extractHostDataFrom = function(s) {
376  // Sample userAgent strings:
377  // 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 ' +
378  //   '(KHTML, like Gecko) Chrome/15.0.874.106 Safari/535.2'
379  // 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.8 ' +
380  //   '(KHTML, like Gecko) Chrome/17.0.933.0 Safari/535.8'
381  // 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 ' +
382  //   '(KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1'
383  // 'Mozilla/5.0 (X11; CrOS i686 14.811.154) AppleWebKit/535.1 ' +
384  //   '(KHTML, like Gecko) Chrome/14.0.835.204 Safari/535.1'
385  var match = new RegExp('Windows NT ([0-9\\.]*)').exec(s);
386  if (match && (match.length >= 2)) {
387    return {
388        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_WINDOWS_,
389        'os_version': match[1],
390        'cpu': ''
391    };
392  }
393  match = new RegExp('Linux ([a-zA-Z0-9_]*)').exec(s);
394  if (match && (match.length >= 2)) {
395    return {
396        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_LINUX_,
397        'os_version' : '',
398        'cpu': match[1]
399    };
400  }
401  match = new RegExp('([a-zA-Z]*) Mac OS X ([0-9_]*)').exec(s);
402  if (match && (match.length >= 3)) {
403    return {
404        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_MAC_,
405        'os_version': match[2].replace(/_/g, '.'),
406        'cpu': match[1]
407    };
408  }
409  match = new RegExp('CrOS ([a-zA-Z0-9]*) ([0-9.]*)').exec(s);
410  if (match && (match.length >= 3)) {
411    return {
412        'os_name': remoting.ServerLogEntry.VALUE_OS_NAME_CHROMEOS_,
413        'os_version': match[2],
414        'cpu': match[1]
415    };
416  }
417  return null;
418};
419
420/**
421 * Adds a field specifying the browser version to this log entry.
422 */
423remoting.ServerLogEntry.prototype.addChromeVersionField = function() {
424  var version = remoting.getChromeVersion();
425  if (version != null) {
426    this.set(remoting.ServerLogEntry.KEY_BROWSER_VERSION_, version);
427  }
428};
429
430/**
431 * Adds a field specifying the webapp version to this log entry.
432 */
433remoting.ServerLogEntry.prototype.addWebappVersionField = function() {
434  var manifest = chrome.runtime.getManifest();
435  if (manifest && manifest.version) {
436    this.set(remoting.ServerLogEntry.KEY_WEBAPP_VERSION_, manifest.version);
437  }
438};
439
440/**
441 * Adds a field specifying the mode to this log entry.
442 *
443 * @param {remoting.ClientSession.Mode} mode
444 */
445remoting.ServerLogEntry.prototype.addModeField = function(mode) {
446  this.set(remoting.ServerLogEntry.KEY_MODE_,
447      remoting.ServerLogEntry.getModeField(mode));
448};
449
450/**
451 * Gets the value of the mode field to be put in a log entry.
452 *
453 * @private
454 * @param {remoting.ClientSession.Mode} mode
455 * @return {string}
456 */
457remoting.ServerLogEntry.getModeField = function(mode) {
458  switch(mode) {
459    case remoting.ClientSession.Mode.IT2ME:
460      return remoting.ServerLogEntry.VALUE_MODE_IT2ME_;
461    case remoting.ClientSession.Mode.ME2ME:
462      return remoting.ServerLogEntry.VALUE_MODE_ME2ME_;
463    default:
464      return remoting.ServerLogEntry.VALUE_MODE_UNKNOWN_;
465  }
466};
467