<commit_before>/*    Copyright 2016-2021 Firewalla Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';
const _ = require('lodash');
const log = require('./logger.js')(__filename);

const util = require('util');
const fs = require('fs')

const iptool = require('ip');
var instance = null;
const license = require('../util/license.js');
const rp = require('request-promise');
const sem = require('../sensor/SensorEventManager.js').getInstance();

const rclient = require('../util/redis_manager.js').getRedisClient()
const sclient = require('../util/redis_manager.js').getSubscriptionClient()
const pclient = require('../util/redis_manager.js').getPublishClient()
const { delay } = require('../util/util.js')
const LRU = require('lru-cache');

const platformLoader = require('../platform/PlatformLoader.js');
const platform = platformLoader.getPlatform();
const Mode = require('./Mode.js');

const exec = require('child-process-promise').exec

const serialFiles = ["/sys/block/mmcblk0/device/serial", "/sys/block/mmcblk1/device/serial","/sys/block/sda/device/wwid"];

const bone = require("../lib/Bone.js");

const sss = require('../extension/sysinfo/SysInfo.js');

const Config = require('./config.js');

const fireRouter = require('./FireRouter.js')
const Message = require('./Message.js');

const { Address4, Address6 } = require('ip-address')

var systemDebug = false;

function setSystemDebug(_systemDebug) {
  if (license.getLicense() == null) {
    systemDebug = true;
  } else {
    systemDebug = _systemDebug;
  }
}

setSystemDebug(systemDebug);

const f = require('../net2/Firewalla.js');

const i18n = require('../util/i18n.js');

const dns = require('dns');
// dnscache will override functions in dns
const dnscache = require('../vendor_lib/dnscache/dnscache.js')({
  enable: true,
  ttl: 300,
  cachesize: 1000
});

class SysManager {
  constructor() { // loglevel is already ignored
    if (instance == null) {
      log.info('Initializing SysManager')
      rclient.hdel("sys:network:info", "oper");
      this.multicastlow = iptool.toLong("172.16.17.32");
      this.multicasthigh = iptool.toLong("172.16.58.3");
      this.locals = {};
      this.lastIPTime = 0;
      this.repo = {};
      this.ipIntfCache = new LRU({max: 4096, maxAge: 900 * 1000}); // reduce call to inMySubnets4/6 in getInterfaceViaIP4/6, which is CPU intensive, the cache will be flushed if network info is updated
      this.iptablesReady = false;
      instance = this;
      sem.once('IPTABLES_READY', () => {
        log.info("Iptables is ready");
        this.iptablesReady = true;
      })

      this.ts = Date.now() / 1000;
      log.info("Init", this.ts);
      sclient.on("message", (channel, message) => {
        log.debug("Msg", this.ts, channel, message);
        switch (channel) {
          case "System:Upgrade:Hard":
            this.upgradeEvent = message;
            log.info("[pubsub] System:Upgrade:Hard", this.ts, this.upgradeEvent);
            break;
          case "System:DebugChange":
            if (message === "1") {
              systemDebug = true;
            } else if (message === "0") {
              systemDebug = false;
            } else {
              log.error("invalid message for channel: " + channel);
              return;
            }
            setSystemDebug(systemDebug);
            log.info("[pubsub] System Debug is changed to " + message);
            break;
          case "System:LanguageChange":
            this.language = message;
            i18n.setLocale(this.language);
            break;
          case "System:TimezoneChange":
            this.reloadTimezone().then(() => {
              if (f.isMain()) {
                pclient.publish(Message.MSG_SYS_TIMEZONE_RELOADED, message);
              }
            }).catch((err) => {
              log.error("Failed to reload timezone", err.message);
            });
            break;
          case Message.MSG_SYS_NETWORK_INFO_UPDATED:
            log.info(Message.MSG_SYS_NETWORK_INFO_UPDATED, 'initiate update')
            this.update(() => {
              this.ipIntfCache.reset();
              sem.emitLocalEvent({ type: Message.MSG_SYS_NETWORK_INFO_RELOADED })
            });
            break;
        }
      });
      sclient.subscribe("System:DebugChange");
      sclient.subscribe("System:LanguageChange");
      sclient.subscribe("System:TimezoneChange");
      sclient.subscribe("System:Upgrade:Hard");
      sclient.subscribe(Message.MSG_SYS_NETWORK_INFO_UPDATED);

      sem.on(Message.MSG_FW_FR_RELOADED, () => {
        this.update(() => {
          this.ipIntfCache.reset();
          sem.emitLocalEvent({ type: Message.MSG_SYS_NETWORK_INFO_RELOADED })
        });
      });

      this.reloadTimezone();

      this.license = license.getLicense();

      sem.on("PublicIP:Updated", (event) => {
        if (event.ip)
          this.publicIp = event.ip;
        if (event.ip6s)
          this.publicIp6s = event.ip6s;
        if (event.wanIPs)
          this.publicIps = event.wanIPs;
      });
      sem.on("DDNS:Updated", (event) => {
        log.info("Updating DDNS:", event);
        if (event.ddns) {
          this.ddns = event.ddns;
        }

        if (event.publicIp) {
          this.publicIp = event.publicIp;
        }
      })

      // only in hard upgrade mode
      rclient.get("sys:upgrade", (err, data) => {
        if (data) {
          this.upgradeEvent = data;
        }
      });

      // record firewalla's own server address
      //
      this.resolveServerDNS(true);
      setInterval(() => {
        this.resolveServerDNS(false);
      }, 1000 * 60 * 60 * 8);

      // update system information more often
      setInterval(() => {
        this.update(null);
      }, 1000 * 60 * 20);
    }
    fireRouter.waitTillReady().then(() => {
      this.update((err) => {
        if (err)
          log.error(`Failed to update SysManager after firerouter is ready`, err.message);
        else
          log.info("SysManager initialization complete");
      });
    });

    return instance
  }

  isIptablesReady() {
    return this.iptablesReady
  }

  async waitTillIptablesReady() {
    if (this.iptablesReady) return

    await delay(1000)
    return this.waitTillIptablesReady()
  }

  resolveServerDNS(retry) {
    dns.resolve4('firewalla.encipher.io', (err, addresses) => {
      log.info("resolveServerDNS:", retry, err, addresses, null);
      if (err && retry) {
        setTimeout(() => {
          this.resolveServerDNS(false);
        }, 1000 * 60 * 10);
      } else {
        if (addresses) {
          this.serverIps = addresses;
          log.info("resolveServerDNS:Set", retry, err, this.serverIps, null);
        }
      }
    });
  }

  updateInfo() {
    this.ept = bone.getSysept();
  }

  // config loaded, sys:network:info loaded and interface discovered
  isConfigInitialized() {
    return this.config != null && this.sysinfo && fireRouter.isReady();
  }

  async waitTillInitialized() {
    if (this.config != null && this.sysinfo && fireRouter.isReady())
      return;
    await delay(1000);
    return this.waitTillInitialized();
  }

  version() {
    if (this.config != null && this.config.version != null) {
      return this.config.version;
    } else {
      return "unknown";
    }
  }

  setNeighbor(ip) {
    this.locals[ip] = "1";
    log.debug("Sys:Insert:Local", ip, "***");
  }

  /**
   * Only call release function when the SysManager instance is no longer
   * needed
   */
  release() {
    rclient.quit();
    sclient.quit();
    log.info("Calling release function of SysManager");
  }

  debugOn(callback) {
    rclient.set("system:debug", "1", (err) => {
      systemDebug = true;
      pclient.publish("System:DebugChange", "1");
      callback(err);
    });
  }

  debugOff(callback) {
    rclient.set("system:debug", "0", (err) => {
      systemDebug = false;
      pclient.publish("System:DebugChange", "0");
      callback(err);
    });
  }

  isSystemDebugOn() {
    return systemDebug;
  }

  isBranchJustChanged() {
    return rclient.hgetAsync("sys:config", "branch.changed")
  }

  clearBranchChangeFlag() {
    return rclient.hdelAsync("sys:config", "branch.changed")
  }

  systemRebootedDueToIssue(reset) {
    try {
      if (fs.existsSync("/home/pi/.firewalla/managed_reboot")) {
        log.info("SysManager:RebootDueToIssue");
        if (reset == true) {
          fs.unlinkSync("/home/pi/.firewalla/managed_reboot");
        }
        return true;
      }
    } catch (e) {
      return false;
    }
    return false;
  }

  systemRebootedByUser(reset) {
    try {
      if (fs.existsSync("/home/pi/.firewalla/managed_real_reboot")) {
        log.info("SysManager:RebootByUser");
        if (reset == true) {
          fs.unlinkSync("/home/pi/.firewalla/managed_real_reboot");
        }
        return true;
      }
    } catch (e) {
      return false;
    }
    return false;
  }

  setLanguage(language, callback) {
    callback = callback || function () { }

    this.language = language;
    const theLanguage = i18n.setLocale(this.language);
    if (theLanguage !== this.language) {
      callback(new Error("invalid language"))
      return
    }

    rclient.hset("sys:config", "language", language, (err) => {
      if (err) {
        log.error("Failed to set language " + language + ", err: " + err);
      }
      pclient.publish("System:LanguageChange", language);
      callback(err);
    });
  }

  setLanguageAsync(language) {
    return new Promise((resolve, reject) => {
      this.setLanguage(language, (err) => {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      })
    })
  }

  async reloadTimezone() {
    this.timezone = await rclient.hgetAsync("sys:config", "timezone");
  }

  getTimezone() {
    return this.timezone || "UTC";
  }

  async setTimezone(timezone) {
    if (this.timezone == timezone) {
      return null;
    }
    this.timezone = timezone;
    try {
      await rclient.hsetAsync("sys:config", "timezone", timezone);
      pclient.publish("System:TimezoneChange", timezone);

      await exec(`sudo timedatectl set-timezone ${timezone}`);
      await exec('sudo systemctl restart cron.service');
      await exec('sudo systemctl restart rsyslog');

      return null;
    } catch (err) {
      log.error("Failed to set timezone:", err);
      return err;
    }
  }

  update(callback) {
    if (!callback) callback = () => { }

    return util.callbackify(this.updateAsync).bind(this)(callback)
  }

  async updateAsync() {
    log.debug("Loading sysmanager data from redis");
    try {
      const results = await rclient.hgetallAsync("sys:config")
      if (results && results.language) {
        this.language = results.language;
        i18n.setLocale(this.language);
      }

      if (results && results.timezone) {
        this.timezone = results.timezone;
      }
    } catch (err) {
      log.error('Error getting sys:config', err)
    }

    try {
      const result = await rclient.getAsync("system:debug")
      if (result && result === "1") {
        systemDebug = true;
      } else {
        // by default off
        systemDebug = false;
      }
    } catch (err) {
      log.error('Error getting system:debug', err)
    }

    try {
      const results = await rclient.hgetallAsync("sys:network:info")
      this.sysinfo = results;

      if (this.sysinfo === null) {
        throw new Error('Empty key');
      }

      this.macMap = {}
      for (let r in this.sysinfo) {
        const item = JSON.parse(this.sysinfo[r])
        this.sysinfo[r] = item
        if (item.mac_address) {
          this.macMap[item.mac_address] = item
        }

        if (item.subnet) {
          this.sysinfo[r].subnetAddress4 = new Address4(item.subnet)
        }
      }

      this.config = Config.getConfig(true)
      if (this.sysinfo['oper'] == null) {
        this.sysinfo.oper = {};
      }
      this.ddns = this.sysinfo["ddns"];
      this.publicIp = this.sysinfo["publicIp"];<commit_msg>check if item is null in SysManager.updateAsync<commit_after>/*    Copyright 2016-2021 Firewalla Inc.
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

'use strict';
const _ = require('lodash');
const log = require('./logger.js')(__filename);

const util = require('util');
const fs = require('fs')

const iptool = require('ip');
var instance = null;
const license = require('../util/license.js');
const rp = require('request-promise');
const sem = require('../sensor/SensorEventManager.js').getInstance();

const rclient = require('../util/redis_manager.js').getRedisClient()
const sclient = require('../util/redis_manager.js').getSubscriptionClient()
const pclient = require('../util/redis_manager.js').getPublishClient()
const { delay } = require('../util/util.js')
const LRU = require('lru-cache');

const platformLoader = require('../platform/PlatformLoader.js');
const platform = platformLoader.getPlatform();
const Mode = require('./Mode.js');

const exec = require('child-process-promise').exec

const serialFiles = ["/sys/block/mmcblk0/device/serial", "/sys/block/mmcblk1/device/serial","/sys/block/sda/device/wwid"];

const bone = require("../lib/Bone.js");

const sss = require('../extension/sysinfo/SysInfo.js');

const Config = require('./config.js');

const fireRouter = require('./FireRouter.js')
const Message = require('./Message.js');

const { Address4, Address6 } = require('ip-address')

var systemDebug = false;

function setSystemDebug(_systemDebug) {
  if (license.getLicense() == null) {
    systemDebug = true;
  } else {
    systemDebug = _systemDebug;
  }
}

setSystemDebug(systemDebug);

const f = require('../net2/Firewalla.js');

const i18n = require('../util/i18n.js');

const dns = require('dns');
// dnscache will override functions in dns
const dnscache = require('../vendor_lib/dnscache/dnscache.js')({
  enable: true,
  ttl: 300,
  cachesize: 1000
});

class SysManager {
  constructor() { // loglevel is already ignored
    if (instance == null) {
      log.info('Initializing SysManager')
      rclient.hdel("sys:network:info", "oper");
      this.multicastlow = iptool.toLong("172.16.17.32");
      this.multicasthigh = iptool.toLong("172.16.58.3");
      this.locals = {};
      this.lastIPTime = 0;
      this.repo = {};
      this.ipIntfCache = new LRU({max: 4096, maxAge: 900 * 1000}); // reduce call to inMySubnets4/6 in getInterfaceViaIP4/6, which is CPU intensive, the cache will be flushed if network info is updated
      this.iptablesReady = false;
      instance = this;
      sem.once('IPTABLES_READY', () => {
        log.info("Iptables is ready");
        this.iptablesReady = true;
      })

      this.ts = Date.now() / 1000;
      log.info("Init", this.ts);
      sclient.on("message", (channel, message) => {
        log.debug("Msg", this.ts, channel, message);
        switch (channel) {
          case "System:Upgrade:Hard":
            this.upgradeEvent = message;
            log.info("[pubsub] System:Upgrade:Hard", this.ts, this.upgradeEvent);
            break;
          case "System:DebugChange":
            if (message === "1") {
              systemDebug = true;
            } else if (message === "0") {
              systemDebug = false;
            } else {
              log.error("invalid message for channel: " + channel);
              return;
            }
            setSystemDebug(systemDebug);
            log.info("[pubsub] System Debug is changed to " + message);
            break;
          case "System:LanguageChange":
            this.language = message;
            i18n.setLocale(this.language);
            break;
          case "System:TimezoneChange":
            this.reloadTimezone().then(() => {
              if (f.isMain()) {
                pclient.publish(Message.MSG_SYS_TIMEZONE_RELOADED, message);
              }
            }).catch((err) => {
              log.error("Failed to reload timezone", err.message);
            });
            break;
          case Message.MSG_SYS_NETWORK_INFO_UPDATED:
            log.info(Message.MSG_SYS_NETWORK_INFO_UPDATED, 'initiate update')
            this.update(() => {
              this.ipIntfCache.reset();
              sem.emitLocalEvent({ type: Message.MSG_SYS_NETWORK_INFO_RELOADED })
            });
            break;
        }
      });
      sclient.subscribe("System:DebugChange");
      sclient.subscribe("System:LanguageChange");
      sclient.subscribe("System:TimezoneChange");
      sclient.subscribe("System:Upgrade:Hard");
      sclient.subscribe(Message.MSG_SYS_NETWORK_INFO_UPDATED);

      sem.on(Message.MSG_FW_FR_RELOADED, () => {
        this.update(() => {
          this.ipIntfCache.reset();
          sem.emitLocalEvent({ type: Message.MSG_SYS_NETWORK_INFO_RELOADED })
        });
      });

      this.reloadTimezone();

      this.license = license.getLicense();

      sem.on("PublicIP:Updated", (event) => {
        if (event.ip)
          this.publicIp = event.ip;
        if (event.ip6s)
          this.publicIp6s = event.ip6s;
        if (event.wanIPs)
          this.publicIps = event.wanIPs;
      });
      sem.on("DDNS:Updated", (event) => {
        log.info("Updating DDNS:", event);
        if (event.ddns) {
          this.ddns = event.ddns;
        }

        if (event.publicIp) {
          this.publicIp = event.publicIp;
        }
      })

      // only in hard upgrade mode
      rclient.get("sys:upgrade", (err, data) => {
        if (data) {
          this.upgradeEvent = data;
        }
      });

      // record firewalla's own server address
      //
      this.resolveServerDNS(true);
      setInterval(() => {
        this.resolveServerDNS(false);
      }, 1000 * 60 * 60 * 8);

      // update system information more often
      setInterval(() => {
        this.update(null);
      }, 1000 * 60 * 20);
    }
    fireRouter.waitTillReady().then(() => {
      this.update((err) => {
        if (err)
          log.error(`Failed to update SysManager after firerouter is ready`, err.message);
        else
          log.info("SysManager initialization complete");
      });
    });

    return instance
  }

  isIptablesReady() {
    return this.iptablesReady
  }

  async waitTillIptablesReady() {
    if (this.iptablesReady) return

    await delay(1000)
    return this.waitTillIptablesReady()
  }

  resolveServerDNS(retry) {
    dns.resolve4('firewalla.encipher.io', (err, addresses) => {
      log.info("resolveServerDNS:", retry, err, addresses, null);
      if (err && retry) {
        setTimeout(() => {
          this.resolveServerDNS(false);
        }, 1000 * 60 * 10);
      } else {
        if (addresses) {
          this.serverIps = addresses;
          log.info("resolveServerDNS:Set", retry, err, this.serverIps, null);
        }
      }
    });
  }

  updateInfo() {
    this.ept = bone.getSysept();
  }

  // config loaded, sys:network:info loaded and interface discovered
  isConfigInitialized() {
    return this.config != null && this.sysinfo && fireRouter.isReady();
  }

  async waitTillInitialized() {
    if (this.config != null && this.sysinfo && fireRouter.isReady())
      return;
    await delay(1000);
    return this.waitTillInitialized();
  }

  version() {
    if (this.config != null && this.config.version != null) {
      return this.config.version;
    } else {
      return "unknown";
    }
  }

  setNeighbor(ip) {
    this.locals[ip] = "1";
    log.debug("Sys:Insert:Local", ip, "***");
  }

  /**
   * Only call release function when the SysManager instance is no longer
   * needed
   */
  release() {
    rclient.quit();
    sclient.quit();
    log.info("Calling release function of SysManager");
  }

  debugOn(callback) {
    rclient.set("system:debug", "1", (err) => {
      systemDebug = true;
      pclient.publish("System:DebugChange", "1");
      callback(err);
    });
  }

  debugOff(callback) {
    rclient.set("system:debug", "0", (err) => {
      systemDebug = false;
      pclient.publish("System:DebugChange", "0");
      callback(err);
    });
  }

  isSystemDebugOn() {
    return systemDebug;
  }

  isBranchJustChanged() {
    return rclient.hgetAsync("sys:config", "branch.changed")
  }

  clearBranchChangeFlag() {
    return rclient.hdelAsync("sys:config", "branch.changed")
  }

  systemRebootedDueToIssue(reset) {
    try {
      if (fs.existsSync("/home/pi/.firewalla/managed_reboot")) {
        log.info("SysManager:RebootDueToIssue");
        if (reset == true) {
          fs.unlinkSync("/home/pi/.firewalla/managed_reboot");
        }
        return true;
      }
    } catch (e) {
      return false;
    }
    return false;
  }

  systemRebootedByUser(reset) {
    try {
      if (fs.existsSync("/home/pi/.firewalla/managed_real_reboot")) {
        log.info("SysManager:RebootByUser");
        if (reset == true) {
          fs.unlinkSync("/home/pi/.firewalla/managed_real_reboot");
        }
        return true;
      }
    } catch (e) {
      return false;
    }
    return false;
  }

  setLanguage(language, callback) {
    callback = callback || function () { }

    this.language = language;
    const theLanguage = i18n.setLocale(this.language);
    if (theLanguage !== this.language) {
      callback(new Error("invalid language"))
      return
    }

    rclient.hset("sys:config", "language", language, (err) => {
      if (err) {
        log.error("Failed to set language " + language + ", err: " + err);
      }
      pclient.publish("System:LanguageChange", language);
      callback(err);
    });
  }

  setLanguageAsync(language) {
    return new Promise((resolve, reject) => {
      this.setLanguage(language, (err) => {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      })
    })
  }

  async reloadTimezone() {
    this.timezone = await rclient.hgetAsync("sys:config", "timezone");
  }

  getTimezone() {
    return this.timezone || "UTC";
  }

  async setTimezone(timezone) {
    if (this.timezone == timezone) {
      return null;
    }
    this.timezone = timezone;
    try {
      await rclient.hsetAsync("sys:config", "timezone", timezone);
      pclient.publish("System:TimezoneChange", timezone);

      await exec(`sudo timedatectl set-timezone ${timezone}`);
      await exec('sudo systemctl restart cron.service');
      await exec('sudo systemctl restart rsyslog');

      return null;
    } catch (err) {
      log.error("Failed to set timezone:", err);
      return err;
    }
  }

  update(callback) {
    if (!callback) callback = () => { }

    return util.callbackify(this.updateAsync).bind(this)(callback)
  }

  async updateAsync() {
    log.debug("Loading sysmanager data from redis");
    try {
      const results = await rclient.hgetallAsync("sys:config")
      if (results && results.language) {
        this.language = results.language;
        i18n.setLocale(this.language);
      }

      if (results && results.timezone) {
        this.timezone = results.timezone;
      }
    } catch (err) {
      log.error('Error getting sys:config', err)
    }

    try {
      const result = await rclient.getAsync("system:debug")
      if (result && result === "1") {
        systemDebug = true;
      } else {
        // by default off
        systemDebug = false;
      }
    } catch (err) {
      log.error('Error getting system:debug', err)
    }

    try {
      const results = await rclient.hgetallAsync("sys:network:info")
      this.sysinfo = results;

      if (this.sysinfo === null) {
        throw new Error('Empty key');
      }

      this.macMap = {}
      for (let r in this.sysinfo) {
        const item = JSON.parse(this.sysinfo[r])
        this.sysinfo[r] = item
        if (item && item.mac_address) {
          this.macMap[item.mac_address] = item
        }

        if (item && item.subnet) {
          this.sysinfo[r].subnetAddress4 = new Address4(item.subnet)
        }
      }

      this.config = Config.getConfig(true)
      if (this.sysinfo['oper'] == null) {
        this.sysinfo.oper = {};
      }
      this.ddns = this.sysinfo["ddns"];
      this.publicIp = this.sysinfo["publicIp"];
{'fraction_non_alphanumeric': 0.14291933824111455, 'fraction_numerical': 0.009103142563128315, 'mean_word_length': 2.409851551956815, 'pattern_counts': {'":': 8, '<': 5, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2439974', 'n_tokens_mistral': 7992, 'n_tokens_neox': 7716, 'n_words': 1633}