From 9b859cd5252cd1eb36f4ba543b77830171058a77 Mon Sep 17 00:00:00 2001 From: Bramha Ghosh Date: Thu, 25 Jan 2018 11:09:18 -0800 Subject: [PATCH] Fix marbl reader bug and adds a marbl viewer cli tool (#223) * fix marbl reader * add viewer cli tool --- cmd/marbl/viewer.go | 107 ++++++++++++++++++++++++++++++++++++++++++++ marbl/reader.go | 35 +++++++++++---- 2 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 cmd/marbl/viewer.go diff --git a/cmd/marbl/viewer.go b/cmd/marbl/viewer.go new file mode 100644 index 000000000..dcff44c26 --- /dev/null +++ b/cmd/marbl/viewer.go @@ -0,0 +1,107 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// 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 +// +// http://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. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Command-line tool to view .marbl files. This tool reads all headers from provided .marbl +// file and prints them to stdout. Bodies of request/response are not printed to stdout, +// instead they are saved into individual files in form of "marbl_ID_TYPE" where +// ID is the ID of request or response and TYPE is "request" or "response". +// +// Command line arguments: +// --file Path to the .marbl file to view. +// --out Optional, folder where this tool will save request/response bodies. +// uses current folder by default. +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + + "github.com/google/martian/marbl" +) + +var ( + file = flag.String("file", "", ".marbl file to show contents of") + out = flag.String("out", "", "folder to write request/response bodies to. Folder must exist.") +) + +func main() { + flag.Parse() + + if *file == "" { + fmt.Println("--file flag is required") + return + } + + file, err := os.Open(*file) + if err != nil { + log.Fatal(err) + } + + reader := marbl.NewReader(file) + + // Iterate through all frames in .marbl file. + for { + frame, err := reader.ReadFrame() + if frame == nil && err == io.EOF { + break + } + if err != nil { + log.Fatalf("reader.ReadFrame(): got %v, want no error or io.EOF\n", err) + break + } + + // Print current frame to stdout. + if frame.FrameType() == marbl.HeaderFrame { + fmt.Print("Header ") + } else { + fmt.Print("Data ") + } + fmt.Println(frame.String()) + + // If frame is Data then we write it into separate + // file that can be inspected later. + if frame.FrameType() == marbl.DataFrame { + df := frame.(marbl.Data) + var t string + if df.MessageType == marbl.Request { + t = "request" + } else if df.MessageType == marbl.Response { + t = "response" + } else { + t = fmt.Sprintf("unknown_%d", df.MessageType) + } + fout := fmt.Sprintf("marbl_%s_%s", df.ID, t) + if *out != "" { + fout = *out + "/" + fout + } + fmt.Printf("Appending data to file %s\n", fout) + + // Append data to the file. Note that body can be split + // into multiple frames so we have to append and not overwrite. + f, err := os.OpenFile(fout, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + if _, err := f.Write(df.Data); err != nil { + log.Fatal(err) + } + if err := f.Close(); err != nil { + log.Fatal(err) + } + } + } +} diff --git a/marbl/reader.go b/marbl/reader.go index 1259e2bfa..7f97247fb 100644 --- a/marbl/reader.go +++ b/marbl/reader.go @@ -34,27 +34,36 @@ func (hf Header) String() string { return fmt.Sprintf("ID=%s; Type=%d; Name=%s; Value=%s", hf.ID, hf.MessageType, hf.Name, hf.Value) } +// FrameType returns HeaderFrame +func (hf Header) FrameType() FrameType { + return HeaderFrame +} + // Data is the payload (body) of the request or response. type Data struct { ID string MessageType MessageType + Index uint32 + Terminal bool Data []byte } // String returns the contents of a Data frame in a format appropriate for debugging and runtime logging. The -// first 20 characters of the payload are emitted. +// contents of the data content slice (df.Data) is not printed, instead the length of Data is printed. func (df Data) String() string { - dl := len(df.Data) - if dl > 20 { - dl = 20 - } + return fmt.Sprintf("ID=%s; Type=%d; Index=%d; Terminal=%t; Data length=%d", + df.ID, df.MessageType, df.Index, df.Terminal, len(df.Data)) +} - return fmt.Sprintf("ID=%s; Type=%d; Data=%q", df.ID, df.MessageType, df.Data[:dl]) +// FrameType returns DataFrame +func (df Data) FrameType() FrameType { + return DataFrame } // Frame describes the interface for a frame (either Data or Header). type Frame interface { String() string + FrameType() FrameType } // Reader wraps a buffered Reader that reads from the io.Reader and emits Frames. @@ -107,12 +116,20 @@ func (r *Reader) ReadFrame() (Frame, error) { MessageType: MessageType(fh[1]), } - dlen := make([]byte, 4) - if _, err := io.ReadFull(r.r, dlen); err != nil { + // Reading 9 bytes: + // 4 bytes index + // 1 byte terminal + // 4 bytes data length + desc := make([]byte, 9) + if _, err := io.ReadFull(r.r, desc); err != nil { return nil, err } - dl := binary.BigEndian.Uint32(dlen[:4]) + df.Index = binary.BigEndian.Uint32(desc[:4]) + df.Terminal = desc[4] == 1 + + dl := binary.BigEndian.Uint32(desc[5:]) + data := make([]byte, int(dl)) if _, err := io.ReadFull(r.r, data); err != nil {