notary/internal/tlog: update ParseTilePath for tile/H/data/... paths

These paths provide access to the raw record data,
in the form returned by /lookup,
separated by blank lines.

Change-Id: Ib8aa7d45815a16043d5f58f0e81b0678701ff2ea
Reviewed-on: https://go-review.googlesource.com/c/exp/+/172412
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
diff --git a/notary/internal/tlog/tile.go b/notary/internal/tlog/tile.go
index 1885e8e..694d89c 100644
--- a/notary/internal/tlog/tile.go
+++ b/notary/internal/tlog/tile.go
@@ -29,10 +29,14 @@
 // Tile{H: 3, L: 4, N: 1234067, W: 8}'s path
 // is tile/3/4/x001/x234/067.
 // See Tile's Path method and the ParseTilePath function.
+//
+// The special level L=-1 holds raw record data instead of hashes.
+// In this case, the level encodes into a tile path as the path element
+// "data" instead of "-1".
 type Tile struct {
 	H int   // height of tile (1 ≤ H ≤ 30)
-	L int   // level in tiling (1 ≤ H ≤ 63)
-	N int64 // number within level (unbounded)
+	L int   // level in tiling (-1 ≤ L ≤ 63)
+	N int64 // number within level (0 ≤ N, unbounded)
 	W int   // width of tile (1 ≤ W ≤ 2**H; 2**H is complete tile)
 }
 
@@ -168,7 +172,13 @@
 	if t.W != 1<<uint(t.H) {
 		pStr = fmt.Sprintf(".p/%d", t.W)
 	}
-	return fmt.Sprintf("tile/%d/%d/%s%s", t.H, t.L, nStr, pStr)
+	var L string
+	if t.L == -1 {
+		L = "data"
+	} else {
+		L = fmt.Sprintf("%d", t.L)
+	}
+	return fmt.Sprintf("tile/%d/%s/%s%s", t.H, L, nStr, pStr)
 }
 
 // ParseTilePath parses a tile coordinate path.
@@ -178,6 +188,11 @@
 		return Tile{}, &badPathError{path}
 	}
 	h, err1 := strconv.Atoi(f[1])
+	isData := false
+	if f[2] == "data" {
+		isData = true
+		f[2] = "0"
+	}
 	l, err2 := strconv.Atoi(f[2])
 	if err1 != nil || err2 != nil || h < 1 || l < 0 || h > 30 {
 		return Tile{}, &badPathError{path}
@@ -201,6 +216,9 @@
 		}
 		n = n*pathBase + int64(nn)
 	}
+	if isData {
+		l = -1
+	}
 	t := Tile{H: h, L: l, N: n, W: w}
 	if path != t.Path() {
 		return Tile{}, &badPathError{path}
diff --git a/notary/internal/tlog/tlog_test.go b/notary/internal/tlog/tlog_test.go
index 3dad1a1..584e728 100644
--- a/notary/internal/tlog/tlog_test.go
+++ b/notary/internal/tlog/tlog_test.go
@@ -238,18 +238,31 @@
 	{"tile/3/5/x123/x456/078", Tile{3, 5, 123456078, 8}},
 	{"tile/3/5/x123/x456/078.p/2", Tile{3, 5, 123456078, 2}},
 	{"tile/1/0/x003/x057/500", Tile{1, 0, 3057500, 2}},
+	{"tile/3/5/123/456/078", Tile{}},
+	{"tile/3/-1/123/456/078", Tile{}},
+	{"tile/1/data/x003/x057/500", Tile{1, -1, 3057500, 2}},
 }
 
 func TestTilePath(t *testing.T) {
 	for _, tt := range tilePaths {
-		p := tt.tile.Path()
-		if p != tt.path {
-			t.Errorf("%+v.Path() = %q, want %q", tt.tile, p, tt.path)
+		if tt.tile.H > 0 {
+			p := tt.tile.Path()
+			if p != tt.path {
+				t.Errorf("%+v.Path() = %q, want %q", tt.tile, p, tt.path)
+			}
 		}
 		tile, err := ParseTilePath(tt.path)
 		if err != nil {
+			if tt.tile.H == 0 {
+				// Expected error.
+				continue
+			}
 			t.Errorf("ParseTilePath(%q): %v", tt.path, err)
 		} else if tile != tt.tile {
+			if tt.tile.H == 0 {
+				t.Errorf("ParseTilePath(%q): expected error, got %+v", tt.path, tt.tile)
+				continue
+			}
 			t.Errorf("ParseTilePath(%q) = %+v, want %+v", tt.path, tile, tt.tile)
 		}
 	}