pkg/ycd/header.go (93 lines of code) (raw):
// Copyright 2022 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
//
// 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.
package ycd
import (
"bufio"
"fmt"
"strconv"
"strings"
)
type Header struct {
// FileVersion is the version of the ycd file.
// Currently it's 1.1.0 and this code is tested against the version.
FileVersion string
// Radis is the radix of the file. 10 or 16.
Radix int
// FirstDigits is always the first several digits of pi?
// e.g. 3.14159265358979323846264338327950288419716939937510 for decimal and
// 3.243f6a8885a308d313198a2e03707344a4093822299f31d008 for hexadecimal.
FirstDigits string
// TotalDigits is zero if the file has n == BlockSize.
// otherwise it's the number of digits in the file.
TotalDigits int64
// BlockSize is digits per file.
BlockSize int64
// BlockID is the position of the current file.
BlockID int64
// Length is the total byte length of the header in the file.
// It is the offset of the empty line after EndHeader.
Length int
}
func parseInt64(s string) (int64, error) {
return strconv.ParseInt(s, 10, 64)
}
func (h *Header) validate() error {
if h.FileVersion != "1.1.0" {
return fmt.Errorf("unknown file version: %s", h.FileVersion)
}
if h.Radix != 10 && h.Radix != 16 {
return fmt.Errorf("unknown radix: %v", h.Radix)
}
return nil
}
func parseHeader(reader *bufio.Reader) (*Header, error) {
var h Header
length := 0
line, err := reader.ReadString('\n')
if err != nil {
return nil, err
}
length += len(line)
if strings.TrimSpace(line) != "#Compressed Digit File" {
return nil, fmt.Errorf("first line should be '#Compressed Digit File': %s", strings.TrimSpace(line))
}
for {
line, err := reader.ReadString('\n')
if err != nil {
return nil, err
}
length += len(line)
// The delimiter is \r\n so it's two runes.
if len(line) == 2 {
continue
}
tokens := strings.Split(line, ":")
key := strings.TrimSpace(tokens[0])
if key == "EndHeader" {
break
}
value := strings.TrimSpace(tokens[1])
switch key {
case "FileVersion":
h.FileVersion = value
case "Base":
if i, err := strconv.Atoi(value); err == nil {
h.Radix = i
} else {
return nil, err
}
case "FirstDigits":
h.FirstDigits = value
case "TotalDigits":
if i, err := parseInt64(value); err == nil {
h.TotalDigits = i
} else {
return nil, err
}
case "Blocksize":
if i, err := parseInt64(value); err == nil {
h.BlockSize = i
} else {
return nil, err
}
case "BlockID":
if i, err := parseInt64(value); err == nil {
h.BlockID = i
} else {
return nil, err
}
default:
return nil, fmt.Errorf("unknown header key: %s", key)
}
}
if err := h.validate(); err != nil {
return nil, err
}
h.Length = length
return &h, nil
}