Merge "Revert "ginstall: add flash unlock support for gflt""
diff --git a/Makefile b/Makefile
index f433d89..0d5a5da 100644
--- a/Makefile
+++ b/Makefile
@@ -84,6 +84,10 @@
 DIRS+=presterastats
 endif
 
+ifeq ($(BUILD_LEDPATTERN),y)
+DIRS+=ledpattern
+endif
+
 PREFIX=/usr
 BINDIR=$(DESTDIR)$(PREFIX)/bin
 LIBDIR=$(DESTDIR)$(PREFIX)/lib
diff --git a/cmds/host-test-ssdptax.sh b/cmds/host-test-ssdptax.sh
index 3b12fdf..ebb3ae7 100755
--- a/cmds/host-test-ssdptax.sh
+++ b/cmds/host-test-ssdptax.sh
@@ -5,12 +5,21 @@
 . ./wvtest/wvtest.sh
 
 SSDP=./host-ssdptax
-
 FIFO="/tmp/ssdptax.test.$$"
-python ./ssdptax-test-server.py "$FIFO" &
-sleep 0.5
 
 WVSTART "ssdptax test"
-WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax"
 
+python ./ssdptax-test-server.py "$FIFO" 1 &
+sleep 0.5
+WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax"
+rm "$FIFO"
+
+python ./ssdptax-test-server.py "$FIFO" 2 &
+sleep 0.5
+WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 REDACTED;server type"
+rm "$FIFO"
+
+python ./ssdptax-test-server.py "$FIFO" 3 &
+sleep 0.5
+WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Unknown;server type"
 rm "$FIFO"
diff --git a/cmds/hostnamelookup.gperf b/cmds/hostnamelookup.gperf
index 0330867..5c3526f 100644
--- a/cmds/hostnamelookup.gperf
+++ b/cmds/hostnamelookup.gperf
@@ -75,10 +75,16 @@
 NP-1P| "Roku", "Roku LT 2700"
 NP-1X| "Roku", "Roku 1 2710"
 NP-2L| "Roku", "Roku Streaming Stick 3500"
+NP-2N| "Roku TV", "Roku TV"
 NP-41| "Roku", "Roku 3 4200"
+NP-4A| "Roku", "Roku 2 4210X"
 NP-4E| "Roku", "Roku 3 4230RW"
+NP-5F| "Roku", "Roku 2 4210X"
+NP-5G| "Roku", "Roku 3 4230X"
+NP-5S| "Roku", "Roku Streaming Stick 3600"
 NP-5Y| "Roku", "Roku 2 4210"
 NP-63| "Roku", "Roku 3 4230"
+NP-YW| "Roku TV", "Roku TV"
 NP-YY| "Roku", "Roku 4 4400"
 OBi200%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi200"
 OBi202%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi202"
diff --git a/cmds/ssdptax-test-server.py b/cmds/ssdptax-test-server.py
index c0346cb..54831d4 100644
--- a/cmds/ssdptax-test-server.py
+++ b/cmds/ssdptax-test-server.py
@@ -8,18 +8,39 @@
 import sys
 
 
+text_device_xml = """<root>
+  <specVersion><major>1</major><minor>0</minor></specVersion>
+  <device><friendlyName>Test Device</friendlyName>
+  <manufacturer>Google Fiber</manufacturer>
+  <modelDescription>Unit Test</modelDescription>
+  <modelName>ssdptax</modelName>
+</device></root>"""
+
+
+email_address_xml = """<root>
+  <specVersion><major>1</major><minor>0</minor></specVersion>
+  <device><friendlyName>FOOBAR: foo@example.com:</friendlyName>
+  <manufacturer>Google Fiber</manufacturer>
+  <modelDescription>Unit Test</modelDescription>
+  <modelName>ssdptax</modelName>
+</device></root>"""
+
+
+no_friendlyname_xml = """<root>
+  <specVersion><major>1</major><minor>0</minor></specVersion>
+  <device></device></root>"""
+
+
+xml = ['']
+
+
 class XmlHandler(BaseHTTPServer.BaseHTTPRequestHandler):
   def do_GET(self):
     self.send_response(200)
     self.send_header('Content-type','text/xml')
     self.end_headers()
-    self.wfile.write("""<root>
-        <specVersion><major>1</major><minor>0</minor></specVersion>
-        <device><friendlyName>Test Device</friendlyName>
-        <manufacturer>Google Fiber</manufacturer>
-        <modelDescription>Unit Test</modelDescription>
-        <modelName>ssdptax</modelName>
-    </device></root>""")
+    self.wfile.write(xml[0])
+
 
 def main():
   un = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -27,6 +48,14 @@
   un.listen(1)
   conn, _ = un.accept()
 
+  testnum = int(sys.argv[2])
+  if testnum == 1:
+    xml[0] = text_device_xml
+  if testnum == 2:
+    xml[0] = email_address_xml
+  if testnum == 3:
+    xml[0] = no_friendlyname_xml
+
   s = BaseHTTPServer.HTTPServer(("", 0), XmlHandler)
   sn = s.socket.getsockname()
   port = sn[1]
diff --git a/cmds/ssdptax.cc b/cmds/ssdptax.cc
index 2a991cb..2a06c7a 100644
--- a/cmds/ssdptax.cc
+++ b/cmds/ssdptax.cc
@@ -31,6 +31,7 @@
 #include <curl/curl.h>
 #include <getopt.h>
 #include <netinet/in.h>
+#include <regex.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -172,6 +173,27 @@
 
 
 /*
+ * Returns true if the friendlyName appears to include an email address.
+ */
+bool contains_email_address(const std::string &friendlyName)
+{
+  regex_t r_email;
+  int rc;
+
+  if (regcomp(&r_email, ".+@[a-z0-9.-]+\\.[a-z0-9.-]+",
+        REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
+    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
+    exit(1);
+  }
+
+  rc = regexec(&r_email, friendlyName.c_str(), 0, NULL, 0);
+  regfree(&r_email);
+
+  return (rc == 0);
+}
+
+
+/*
  * Combine the manufacturer and model. If the manufacturer name
  * is already present in the model string, don't duplicate it.
  */
@@ -206,7 +228,9 @@
   }
 
   mac = get_l2addr_for_ip(info->ipaddr);
-  if (info->friendlyName.length() > 0) {
+  if (contains_email_address(info->friendlyName)) {
+    result = "ssdp " + mac + " REDACTED;" + info->srv_type;
+  } else if (info->friendlyName.length() > 0) {
     result = "ssdp " + mac + " " + info->friendlyName + ";" +
       unfriendly_name(info->manufacturer, info->model);
   } else {
diff --git a/ledpattern/Makefile b/ledpattern/Makefile
new file mode 100644
index 0000000..705a398
--- /dev/null
+++ b/ledpattern/Makefile
@@ -0,0 +1,28 @@
+default:
+
+PREFIX=/
+BINDIR=$(DESTDIR)$(PREFIX)/bin
+PYTHON?=python
+
+all:
+
+install:
+	mkdir -p $(BINDIR)
+	cp ledpattern.py $(BINDIR)/ledpattern
+
+install-libs:
+	@echo "No libs to install."
+
+test: lint
+	set -e; \
+	for pytest in $(wildcard *_test.py); do \
+		echo; \
+		echo "Testing $$pytest"; \
+		$(PYTHON) $$pytest; \
+	done
+
+clean:
+	rm -rf *.pyc
+
+lint:
+	gpylint ledpattern.py ledpattern_test.py
diff --git a/ledpattern/ledpattern.py b/ledpattern/ledpattern.py
new file mode 100755
index 0000000..bcd3b03
--- /dev/null
+++ b/ledpattern/ledpattern.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python
+
+"""Blinks a specific LED pattern read from a simple pattern file.
+
+The first value is the state that the LED pattern represents. Followed by
+any combination of the following values:
+
+  R = Red blink
+  B = Blue blink
+  P = Purple blink
+
+An example pattern file might look like:
+
+ echo "blink,R,R,R" > /tmp/test.pat
+
+Invoking "ledpattern.py /tmp/test.pat blink" would result in the red LED
+blinking three times.
+"""
+
+__author__ = 'Chris Gibson <cgibson@google.com>'
+
+import csv
+import os
+import sys
+import time
+
+# Unit tests can override these values.
+DISABLE_GPIOMAILBOX = '/tmp/gpio/disable'
+SYSFS_RED_BRIGHTNESS = '/sys/class/leds/sys-red/brightness'
+SYSFS_BLUE_BRIGHTNESS = '/sys/class/leds/sys-blue/brightness'
+SLEEP_TIMEOUT = 0.5
+
+
+class LedPattern(object):
+  """Read, parse, and blink LEDs based on a pattern from a file."""
+
+  def ReadCsvPatternFile(self, pattern_file, state):
+    """Read a CSV pattern file."""
+    if not os.path.exists(pattern_file):
+      print 'Error: Pattern file: "%s" not found.' % pattern_file
+      return None
+    if not state:
+      print 'Error: led state cannot be empty.'
+      return None
+    try:
+      with open(pattern_file, 'r') as f:
+        patterns = csv.reader(f, delimiter=',')
+        for row in patterns:
+          if row[0] == state:
+            return [c for c in row[1:] if c in ['R', 'B', 'P']]
+      print ('Error: Could not find led state: "%s" in pattern file: %s'
+             % (state, pattern_file))
+      return None
+    except (IOError, OSError) as ex:
+      print ('Failed to open the pattern file: %s, error: %s.'
+             % (pattern_file, ex))
+      return None
+
+  def SetRedBrightness(self, level):
+    with open(SYSFS_RED_BRIGHTNESS, 'w') as f:
+      f.write(level)
+
+  def SetBlueBrightness(self, level):
+    with open(SYSFS_BLUE_BRIGHTNESS, 'w') as f:
+      f.write(level)
+
+  def SetLedsOff(self):
+    self.SetRedBrightness('0')
+    self.SetBlueBrightness('0')
+
+  def RedBlink(self):
+    self.SetLedsOff()
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetRedBrightness('100')
+    self.SetBlueBrightness('0')
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetLedsOff()
+
+  def BlueBlink(self):
+    self.SetLedsOff()
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetRedBrightness('0')
+    self.SetBlueBrightness('100')
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetLedsOff()
+
+  def PurpleBlink(self):
+    self.SetLedsOff()
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetRedBrightness('100')
+    self.SetBlueBrightness('100')
+    time.sleep(SLEEP_TIMEOUT)
+    self.SetLedsOff()
+
+  def PlayPattern(self, pattern):
+    for color in pattern:
+      if color == 'R':
+        self.RedBlink()
+      elif color == 'B':
+        self.BlueBlink()
+      elif color == 'P':
+        self.PurpleBlink()
+
+  def Run(self, pattern_file, state):
+    """Sets up an LED pattern to play.
+
+    Arguments:
+      pattern_file: Pattern file containing a list of LED states/patterns.
+      state: Name of the LED state to play.
+
+    Returns:
+      An integer exit code: 0 means everything succeeded. Non-zero exit codes
+      mean something went wrong.
+    """
+    try:
+      open(DISABLE_GPIOMAILBOX, 'w').close()
+    except (IOError, OSError) as ex:
+      # If we can't disable gpio-mailbox then we can't guarantee control
+      # over the LEDs, so we just have to admit defeat.
+      print 'Error: Failed to disable gpio-mailbox! %s' % ex
+      return 1
+
+    try:
+      pattern = self.ReadCsvPatternFile(pattern_file, state)
+      if not pattern:
+        print 'Reading pattern failed! Exiting!'
+        return 1
+    except (IOError, OSError) as ex:
+      print 'Error: Failed to read pattern file, %s' % ex
+      return 1
+
+    # Depending on what state the LEDs are in when we touched the gpio-mailbox
+    # disable file, the LEDs will remain in that last state. Firstly, turn both
+    # LEDs off then sleep for a second to indicate that the pattern is about
+    # to begin.
+    self.SetLedsOff()
+    time.sleep(1)
+
+    self.PlayPattern(pattern)
+
+    # Turn off the LEDs and sleep for a second to clearly delineate the end of
+    # the current pattern.
+    self.SetLedsOff()
+    time.sleep(1)
+
+    os.unlink(DISABLE_GPIOMAILBOX)
+    return 0
+
+
+def Usage():
+  print 'Usage:'
+  print '  %s {pattern file} {state}' % sys.argv[0]
+  print
+  print '    pattern file: path to a pattern file'
+  print '    state: the LED state to select'
+
+
+if __name__ == '__main__':
+  if len(sys.argv) != 3:
+    Usage()
+    sys.exit(1)
+  ledpattern = LedPattern()
+  sys.exit(ledpattern.Run(sys.argv[1], sys.argv[2]))
diff --git a/ledpattern/ledpattern_test.py b/ledpattern/ledpattern_test.py
new file mode 100755
index 0000000..0a42dad
--- /dev/null
+++ b/ledpattern/ledpattern_test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/python
+
+"""Tests for ledpattern."""
+
+import os
+import tempfile
+import unittest
+
+import ledpattern
+
+
+class TestLedpattern(unittest.TestCase):
+
+  def setUp(self):
+    self.ledpattern = ledpattern.LedPattern()
+    self.fd_red, ledpattern.SYSFS_RED_BRIGHTNESS = tempfile.mkstemp()
+    print ledpattern.SYSFS_RED_BRIGHTNESS
+    self.fd_blue, ledpattern.SYSFS_BLUE_BRIGHTNESS = tempfile.mkstemp()
+    print ledpattern.SYSFS_BLUE_BRIGHTNESS
+
+  def tearDown(self):
+    os.close(self.fd_red)
+    os.close(self.fd_blue)
+    os.unlink(ledpattern.SYSFS_RED_BRIGHTNESS)
+    os.unlink(ledpattern.SYSFS_BLUE_BRIGHTNESS)
+
+  def testReadCsvPatternFileTest(self):
+    expected = ['R', 'B', 'P']
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', 'test')
+    self.assertEqual(expected, actual)
+
+  def testReadCsvPatternFileNotFound(self):
+    actual = self.ledpattern.ReadCsvPatternFile('/does/not/exist.pat', 'test')
+    self.assertEqual(None, actual)
+
+  def testReadCsvPatternFileNoState(self):
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', '')
+    self.assertEqual(None, actual)
+
+  def testReadCsvPatternFileIsMissingState(self):
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat', 'foo')
+    self.assertEqual(None, actual)
+
+  def testReadCsvPatternFileInvalidCharsGetFiltered(self):
+    expected = ['B', 'B', 'B']
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat',
+                                                'test_with_invalid_chars')
+    self.assertEqual(expected, actual)
+
+  def testReadCsvPatternFileEmptyPattern(self):
+    expected = []
+    actual = self.ledpattern.ReadCsvPatternFile('./testdata/test.pat',
+                                                'test_empty_pattern')
+    self.assertEqual(expected, actual)
+
+  def testSetRedBrightness(self):
+    self.ledpattern.SetRedBrightness('foo')
+    with open(ledpattern.SYSFS_RED_BRIGHTNESS, 'r') as f:
+      actual = f.read()
+    self.assertEqual('foo', actual)
+
+  def testSetBlueBrightness(self):
+    self.ledpattern.SetBlueBrightness('bar')
+    with open(ledpattern.SYSFS_BLUE_BRIGHTNESS, 'r') as f:
+      actual = f.read()
+    self.assertEqual('bar', actual)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/ledpattern/testdata/test.pat b/ledpattern/testdata/test.pat
new file mode 100644
index 0000000..6b49acf
--- /dev/null
+++ b/ledpattern/testdata/test.pat
@@ -0,0 +1,3 @@
+test_with_invalid_chars,B,B,B,X,X,X
+test,R,B,P
+test_empty_pattern,,