Merge pull request #12 from vasilvv/mouseover-flag

Make the zoom level required for packet info display configurable
diff --git a/lib/quic_trace.proto b/lib/quic_trace.proto
index 66e3e56..9924634 100644
--- a/lib/quic_trace.proto
+++ b/lib/quic_trace.proto
@@ -1,11 +1,11 @@
 // Copyright 2018 Google LLC
-// 
+//
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 // You may obtain a copy of the License at
-// 
+//
 //     https://www.apache.org/licenses/LICENSE-2.0
-// 
+//
 // Unless required by applicable law or agreed to in writing, software
 // distributed under the License is distributed on an "AS IS" BASIS,
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -117,6 +117,7 @@
   ENCRYPTION_INITIAL = 1;
   ENCRYPTION_0RTT = 2;
   ENCRYPTION_1RTT = 3;
+  ENCRYPTION_HANDSHAKE = 4;
 };
 
 enum EventType {
diff --git a/tools/render/processed_trace.cc b/tools/render/processed_trace.cc
index 81a6645..9f74677 100644
--- a/tools/render/processed_trace.cc
+++ b/tools/render/processed_trace.cc
@@ -16,6 +16,7 @@
 
 #include "absl/algorithm/container.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
 #include "tools/render/layout_constants.h"
 
 namespace quic_trace {
@@ -232,6 +233,37 @@
       return "???";
   }
 }
+
+constexpr int kMaxAckBlocksShown = 3;
+std::string AckSummary(const AckInfo& ack) {
+  if (ack.acked_packets_size() == 0) {
+    return "";
+  }
+
+  bool overflow = false;
+  int blocks_to_show = ack.acked_packets_size();
+  if (blocks_to_show > kMaxAckBlocksShown) {
+    blocks_to_show = kMaxAckBlocksShown;
+    overflow = true;
+  }
+
+  std::vector<std::string> ack_ranges;
+  for (int i = 0; i < blocks_to_show; i++) {
+    const AckBlock& block = ack.acked_packets(i);
+    if (block.first_packet() == block.last_packet()) {
+      ack_ranges.push_back(absl::StrCat(block.first_packet()));
+    } else {
+      ack_ranges.push_back(
+          absl::StrCat(block.first_packet(), ":", block.last_packet()));
+    }
+  }
+
+  std::string result = absl::StrJoin(ack_ranges, ", ");
+  if (overflow) {
+    absl::StrAppend(&result, "...");
+  }
+  return result;
+}
 }  // namespace
 
 void ProcessedTrace::FillTableForPacket(Table* table,
@@ -276,7 +308,7 @@
               absl::StrCat("#", frame.stream_frame_info().stream_id(), ": ",
                            frame.stream_frame_info().offset(), "-",
                            frame.stream_frame_info().offset() +
-                           frame.stream_frame_info().length(),
+                               frame.stream_frame_info().length(),
                            " (", frame.stream_frame_info().length(), ")"));
           break;
         case RESET_STREAM:
@@ -299,6 +331,9 @@
           table->AddRow("Stream blocked",
                         absl::StrCat(frame.flow_control_info().stream_id()));
           break;
+        case ACK:
+          table->AddRow("Ack", AckSummary(frame.ack_info()));
+          break;
         default:
           table->AddRow("Unknown", "");
           break;
diff --git a/tools/render/sdl_util.h b/tools/render/sdl_util.h
index f5380f8..a465418 100644
--- a/tools/render/sdl_util.h
+++ b/tools/render/sdl_util.h
@@ -21,6 +21,8 @@
 #include <GL/glew.h>
 #include <SDL.h>
 
+#include <memory>
+
 #include <glog/logging.h>
 #include <glog/stl_logging.h>
 #include "absl/memory/memory.h"
@@ -39,25 +41,22 @@
 };
 
 // A helper class to automatically delete SDL objects once they go out of scope.
-template <typename T, void (*D)(T)>
+template <typename T, void (*D)(T*)>
 class ScopedSdlType {
  public:
-  ScopedSdlType(T value) : value_(value) {}
-  ~ScopedSdlType() { D(value_); }
+  ScopedSdlType() : value_(nullptr, D) {}
+  ScopedSdlType(T* value) : value_(value, D) {}
 
-  ScopedSdlType(const ScopedSdlType&) = delete;
-  ScopedSdlType& operator=(const ScopedSdlType&) = delete;
-
-  T get() const { return value_; }
-  T operator*() const { return value_; }
-  T operator->() const { return value_; }
+  T* get() const { return value_.get(); }
+  T* operator*() const { return value_.get(); }
+  T* operator->() const { return value_.get(); }
 
  private:
-  T value_;
+  std::unique_ptr<T, void (*)(T*)> value_;
 };
 
-using ScopedSdlWindow = ScopedSdlType<SDL_Window*, SDL_DestroyWindow>;
-using ScopedSdlSurface = ScopedSdlType<SDL_Surface*, SDL_FreeSurface>;
+using ScopedSdlWindow = ScopedSdlType<SDL_Window, SDL_DestroyWindow>;
+using ScopedSdlSurface = ScopedSdlType<SDL_Surface, SDL_FreeSurface>;
 
 class OpenGlContext {
  public:
diff --git a/tools/render/text.cc b/tools/render/text.cc
index 6cf45ad..577ea25 100644
--- a/tools/render/text.cc
+++ b/tools/render/text.cc
@@ -119,14 +119,24 @@
     return cache_it->second.text;
   }
 
-  // Render the text.
   if (TTF_GetFontStyle(font_) != style) {
     TTF_SetFontStyle(font_, style);
   }
-  ScopedSdlSurface surface(
-      TTF_RenderUTF8_Blended(font_, text.c_str(), SDL_Color{0, 0, 0}));
+
+  // Render the text.
+  ScopedSdlSurface surface;
+  if (text.empty()) {
+    // SDL2_ttf returns error when asked to render an empty string.  Return a
+    // 1x1 white square instead.
+    surface = ScopedSdlSurface(
+        SDL_CreateRGBSurfaceWithFormat(0, 1, 1, 32, SDL_PIXELFORMAT_RGBA32));
+    CHECK(!SDL_MUSTLOCK(*surface));
+    memset(surface->pixels, 0xff, surface->h * surface->pitch);
+  } else {
+    surface = TTF_RenderUTF8_Blended(font_, text.c_str(), SDL_Color{0, 0, 0});
+  }
   if (*surface == nullptr) {
-    LOG(FATAL) << "Failed to render text: " << text;
+    LOG(FATAL) << "Failed to render text: \"" << text.size() << "\"";
   }
 
   std::unique_ptr<Text> result(new Text(this));