KVM WORKING! ! !
diff --git a/src/ast_jpeg_decoder_test.cpp b/src/ast_jpeg_decoder_test.cpp
new file mode 100644
index 0000000..bc6857e
--- /dev/null
+++ b/src/ast_jpeg_decoder_test.cpp
@@ -0,0 +1,156 @@
+#include "ast_jpeg_decoder.hpp"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#ifdef BUILD_CIMG
+#define cimg_display 0
+#include <CImg.h>
+#endif
+
+using namespace testing;
+MATCHER_P2(IsBetween, a, b, std::string(negation ? "isn't" : "is") +
+                                " between " + PrintToString(a) + " and " +
+                                PrintToString(b)) {
+  return a <= arg && arg <= b;
+};
+
+TEST(AstJpegDecoder, AllBlue) {
+  AstVideo::RawVideoBuffer out;
+
+  // This binary blog was created on the aspeed hardware using a blue screen
+  // consisting of the color 0x8EFFFA in a web browser window
+  FILE *fp = fopen("test_resources/aspeedbluescreen.bin", "rb");
+  EXPECT_NE(fp, nullptr);
+  size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+                           out.buffer.size() * sizeof(long), fp);
+  fclose(fp);
+
+  ASSERT_GT(bufferlen, 0);
+
+  out.y_selector = 0;
+  out.uv_selector = 0;
+  out.mode = AstVideo::YuvMode::YUV444;
+  out.width = 800;
+  out.height = 600;
+
+  AstVideo::AstJpegDecoder d;
+  d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+           out.uv_selector);
+
+  int tolerance = 16;
+
+  // All pixels should be blue (0x8EFFFA) to within a tolerance (due to jpeg
+  // compression artifacts and quanitization)
+  for (int i = 0; i < out.width * out.height; i++) {
+    AstVideo::RGB &pixel = d.OutBuffer[i];
+    EXPECT_GT(pixel.R, 0x8E - tolerance);
+    EXPECT_LT(pixel.R, 0x8E + tolerance);
+    EXPECT_GT(pixel.G, 0xFF - tolerance);
+    EXPECT_LT(pixel.G, 0xFF + tolerance);
+    EXPECT_GT(pixel.B, 0xF1 - tolerance);
+    EXPECT_LT(pixel.B, 0xF1 + tolerance);
+  }
+}
+
+TEST(AstJpegDecoder, AllBlack) {
+  AstVideo::RawVideoBuffer out;
+
+  // This binary blog was created on the aspeed hardware using a black screen
+  FILE *fp = fopen("test_resources/aspeedblackscreen.bin", "rb");
+  EXPECT_NE(fp, nullptr);
+  size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+                           out.buffer.size() * sizeof(long), fp);
+  fclose(fp);
+
+  ASSERT_GT(bufferlen, 0);
+
+  out.y_selector = 0;
+  out.uv_selector = 0;
+  out.mode = AstVideo::YuvMode::YUV444;
+  out.width = 800;
+  out.height = 600;
+
+  AstVideo::AstJpegDecoder d;
+  d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+           out.uv_selector);
+
+  // All pixels should be blue (0x8EFFFA) to within a tolerance (due to jpeg
+  // compression artifacts and quanitization)
+  for (int x = 0; x < out.width; x++) {
+    for (int y = 0; y < out.height; y++) {
+      AstVideo::RGB pixel = d.OutBuffer[x + (y * out.width)];
+      ASSERT_EQ(pixel.R, 0x00) << "X:" << x << " Y: " << y;
+      ASSERT_EQ(pixel.G, 0x00) << "X:" << x << " Y: " << y;
+      ASSERT_EQ(pixel.B, 0x00) << "X:" << x << " Y: " << y;
+    }
+  }
+}
+
+TEST(AstJpegDecoder, TestColors) {
+  AstVideo::RawVideoBuffer out;
+
+  // This binary blog was created on the aspeed hardware using a blue screen
+  // consisting of the color 0x8EFFFA in a web browser window
+  FILE *fp = fopen("test_resources/ubuntu_444_800x600_0chrom_0lum.bin", "rb");
+  EXPECT_NE(fp, nullptr);
+  size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+                           out.buffer.size() * sizeof(long), fp);
+  fclose(fp);
+
+  ASSERT_GT(bufferlen, 0);
+
+  out.y_selector = 0;
+  out.uv_selector = 0;
+  out.mode = AstVideo::YuvMode::YUV444;
+  out.width = 800;
+  out.height = 600;
+
+  AstVideo::AstJpegDecoder d;
+  d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+           out.uv_selector);
+
+  int tolerance = 16;
+
+  for (int i = 0; i < out.width * out.height; i++) {
+    AstVideo::RGB &pixel = d.OutBuffer[i];
+    EXPECT_GT(pixel.R, 0x8E - tolerance);
+    EXPECT_LT(pixel.R, 0x8E + tolerance);
+    EXPECT_GT(pixel.G, 0xFF - tolerance);
+    EXPECT_LT(pixel.G, 0xFF + tolerance);
+    EXPECT_GT(pixel.B, 0xF1 - tolerance);
+    EXPECT_LT(pixel.B, 0xF1 + tolerance);
+  }
+}
+// Tests the buffers around the screen aren't written to
+TEST(AstJpegDecoder, BufferLimits) {
+  AstVideo::RawVideoBuffer out;
+
+  // This binary blog was created on the aspeed hardware using a black screen
+  FILE *fp = fopen("test_resources/aspeedblackscreen.bin", "rb");
+  EXPECT_NE(fp, nullptr);
+  size_t bufferlen = fread(out.buffer.data(), sizeof(char),
+                           out.buffer.size() * sizeof(long), fp);
+  fclose(fp);
+
+  ASSERT_GT(bufferlen, 0);
+
+  out.y_selector = 0;
+  out.uv_selector = 0;
+  out.mode = AstVideo::YuvMode::YUV444;
+  out.width = 800;
+  out.height = 600;
+
+  AstVideo::AstJpegDecoder d;
+  d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+           out.uv_selector);
+  // Reserved pixel should be default value
+  for (auto &pixel : d.OutBuffer) {
+    EXPECT_EQ(pixel.Reserved, 0xAA);
+  }
+  // All pixels beyond the buffer should be zero
+  for (int i = out.width * out.height; i < d.OutBuffer.size(); i++) {
+    EXPECT_EQ(d.OutBuffer[i].R, 0x00) << "index:" << i;
+    EXPECT_EQ(d.OutBuffer[i].B, 0x00) << "index:" << i;
+    EXPECT_EQ(d.OutBuffer[i].G, 0x00) << "index:" << i;
+  }
+}
\ No newline at end of file
diff --git a/src/getvideo_main.cpp b/src/getvideo_main.cpp
index 50889c9..4f375ca 100644
--- a/src/getvideo_main.cpp
+++ b/src/getvideo_main.cpp
@@ -9,120 +9,68 @@
 #include <fcntl.h>
 #include <unistd.h>
 
+#include <stdio.h>
+#include <stdlib.h>
+#define BUILD_CIMG
+#ifdef BUILD_CIMG
+#define cimg_display 0
+#include <CImg.h>
+#endif
 
-namespace AstVideo {
-class VideoPuller {
- public:
-  VideoPuller() {}
-
-  void initialize() {
-    std::cout << "Opening /dev/video\n";
-    video_fd = open("/dev/video", O_RDWR);
-    if (!video_fd) {
-      std::cout << "Failed to open /dev/video\n";
-    } else {
-      std::cout << "Opened successfully\n";
-    }
-
-    std::vector<unsigned char> buffer(1024 * 1024, 0);
-
-    IMAGE_INFO image_info{};
-    image_info.do_image_refresh = 1;  // full frame refresh
-    image_info.qc_valid = 0;          // quick cursor disabled
-    image_info.parameter.features.w = 800;
-    image_info.parameter.features.h = 600;
-    image_info.parameter.features.chrom_tbl = 0;  // level
-    image_info.parameter.features.lumin_tbl = 0;
-    image_info.parameter.features.jpg_fmt = 1;
-    image_info.parameter.features.buf = buffer.data();
-    image_info.crypttype = -1;
-    std::cout << "Writing\n";
-    
-    int status;
-    
-    status = write(video_fd, reinterpret_cast<char*>(&image_info),
-                        sizeof(image_info));
-    if (status != 0) {
-      std::cout << "Write failed.  Return: " << status <<"\n";
-      perror("perror output:");
-    }
-    
-    std::cout << "Write done\n";
-    //std::this_thread::sleep_for(std::chrono::milliseconds(2000));
-    
-    std::cout << "Reading\n";
-    status = read(video_fd, reinterpret_cast<char*>(&image_info), sizeof(image_info));
-    std::cout << "Reading\n";
-
-    if (status != 0) {
-      std::cout << "Read failed with status " << status << "\n";
-    }
-
-    auto pt = reinterpret_cast<char*>(&image_info);
-
-    for (int i = 0; i < sizeof(image_info); i++) {
-      std::cout << std::hex << std::setfill('0') << std::setw(2)
-                << int(*(pt + i)) << " ";
-    }
-
-    std::cout << "\nprinting buffer\n";
-    
-    for(int i = 0; i < 512; i++){
-        if (i % 16 == 0){
-          std::cout << "\n";
-        }
-        std::cout << std::hex << std::setfill('0') << std::setw(2)
-            << int(buffer[i]) << " ";
-    }
-    
-    buffer.resize(image_info.len);
-    
-    std::ofstream f("/tmp/screen.jpg",std::ios::out | std::ios::binary); 
-
-    f.write(reinterpret_cast<char*>(buffer.data()), buffer.size());
-
-    std::cout << "\n";
-
-    std::cout << "typedef struct _video_features {\n";
-    std::cout << "short jpg_fmt: " << image_info.parameter.features.jpg_fmt
-              << "\n";
-    std::cout << "short lumin_tbl;" << image_info.parameter.features.lumin_tbl
-              << "\n";
-    std::cout << "short chrom_tbl;" << image_info.parameter.features.chrom_tbl
-              << "\n";
-    std::cout << "short tolerance_noise;"
-              << image_info.parameter.features.tolerance_noise << "\n";
-    std::cout << "int w; 0X" << image_info.parameter.features.w << "\n";
-    std::cout << "int h; 0X" << image_info.parameter.features.h << "\n";
-
-    std::cout << "void* buf; 0X" << static_cast<void*>(image_info.parameter.features.buf) << "\n";
-    // std::cout << "unsigned char *buf;" << image_info.parameter.features.buf
-    // << "\n";
-    std::cout << "} FEATURES_TAG;\n";
-
-    std::cout << "typedef struct _image_info {";
-    std::cout << "short do_image_refresh;" << image_info.do_image_refresh
-              << "\n";
-    std::cout << "char qc_valid;" << image_info.qc_valid << "\n";
-    std::cout << "unsigned int len;" << image_info.len << "\n";
-    std::cout << "int crypttype;" << image_info.crypttype << "\n";
-    std::cout << "char cryptkey[16];" << image_info.cryptkey << "\n";
-    std::cout << "union {\n";
-    std::cout << "    FEATURES_TAG features;\n";
-    std::cout << "    AST_CURSOR_TAG cursor_info;\n";
-    std::cout << "} parameter;\n";
-    std::cout << "} IMAGE_INFO;\n";
-    std::cout << std::endl;
-
-    close(video_fd);
-  }
-  int video_fd;
-};
-}
+#include <ast_jpeg_decoder.hpp>
+#include <ast_video_puller.hpp>
 
 int main() {
-  AstVideo::VideoPuller p;
-  p.initialize();
+  std::cout << "Started\n";
+  AstVideo::RawVideoBuffer out;
+  bool have_hardware = false;
+  if( access( "/dev/video", F_OK ) != -1 ) {
+    AstVideo::VideoPuller p;
+    p.initialize();
+    out = p.read_video();
+  } else {
+    FILE *fp = fopen("/home/ed/screendata.bin", "rb");
+    if (fp) {
+      size_t newLen = fread(out.buffer.data(), sizeof(char),
+                            out.buffer.size() * sizeof(long), fp);
+      if (ferror(fp) != 0) {
+        fputs("Error reading file", stderr);
+      }
+      fclose(fp);
+      out.buffer.resize(newLen);
+      out.mode = AstVideo::YuvMode::YUV444;
+      out.width = 800;
+      out.height = 600;
+      out.y_selector = 0;
+      out.uv_selector = 0;
+
+    }
+  }
+
+  FILE *fp = fopen("/tmp/screendata.bin", "wb");
+  fwrite(out.buffer.data(), sizeof(char),
+                            out.buffer.size(), fp);
+
+  AstVideo::AstJpegDecoder d;
+  std::cout << "MODE " << static_cast<int>(out.mode);
+  d.decode(out.buffer, out.width, out.height, out.mode, out.y_selector,
+           out.uv_selector);
+  #ifdef BUILD_CIMG
+  cimg_library::CImg<unsigned char> image(out.width, out.height, 1,
+                                          3 /*numchannels*/);
+  for (int y = 0; y < out.height; y++) {
+    for (int x = 0; x < out.width; x++) {
+      auto pixel = d.OutBuffer[x + (y * out.width)];
+      image(x, y, 0) = pixel.R;
+      image(x, y, 1) = pixel.G;
+      image(x, y, 2) = pixel.B;
+    }
+  }
+  image.save("/tmp/file2.bmp");
+  #endif
+  
+  std::cout << "Done!\n";
+
 
   return 1;
-}
\ No newline at end of file
+}
diff --git a/src/test_resources/aspeedblackscreen.bin b/src/test_resources/aspeedblackscreen.bin
new file mode 100644
index 0000000..537216d
--- /dev/null
+++ b/src/test_resources/aspeedblackscreen.bin
Binary files differ
diff --git a/src/test_resources/aspeedbluescreen.bin b/src/test_resources/aspeedbluescreen.bin
new file mode 100644
index 0000000..829c674
--- /dev/null
+++ b/src/test_resources/aspeedbluescreen.bin
Binary files differ
diff --git a/src/test_resources/ubuntu_444_800x600_0chrom_0lum.bin b/src/test_resources/ubuntu_444_800x600_0chrom_0lum.bin
new file mode 100644
index 0000000..929540c
--- /dev/null
+++ b/src/test_resources/ubuntu_444_800x600_0chrom_0lum.bin
Binary files differ
diff --git a/src/token_authorization_middleware.cpp b/src/token_authorization_middleware.cpp
index a5ecd2d..82a5bde 100644
--- a/src/token_authorization_middleware.cpp
+++ b/src/token_authorization_middleware.cpp
@@ -65,7 +65,9 @@
         std::random_device rand;
         random_bytes_engine rbe;
         std::string token('a', 20);
-        std::generate(begin(token), end(token), std::ref(rbe));
+        // TODO(ed) for some reason clang-tidy finds a divide by zero error in cstdlibc here
+        // commented out for now.  Needs investigation
+        std::generate(begin(token), end(token), std::ref(rbe));  // NOLINT
         std::string encoded_token;
         base64::base64_encode(token, encoded_token);
         ctx.auth_token = encoded_token;