Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
rubiojr committed Sep 30, 2020
0 parents commit a69fbd8
Show file tree
Hide file tree
Showing 28 changed files with 2,440 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/notes.md
26 changes: 26 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
BSD 2-Clause License

Copyright (c) 2014, Alexander Neumann <[email protected]>
Copyright (c) 2020, Sergio Rubio <[email protected]>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Restic API


163 changes: 163 additions & 0 deletions blob/blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package blob

import (
"errors"
"fmt"
"io"

"github.com/rubiojr/rapi/crypto"
"github.com/rubiojr/rapi/restic"
)

// Blob is one part of a file or a tree.
type Blob struct {
Type BlobType
Length uint
ID restic.ID
Offset uint
PackID restic.ID
}

func (b Blob) String() string {
return fmt.Sprintf("<Blob (%v) %v, offset %v, length %v>",
b.Type, b.ID.Str(), b.Offset, b.Length)
}

// PackedBlob is a blob stored within a file.
type PackedBlob struct {
Blob
PackID restic.ID
}

// BlobHandle identifies a blob of a given type.
type BlobHandle struct {
ID restic.ID
Type BlobType
}

func (h BlobHandle) String() string {
return fmt.Sprintf("<%s/%s>", h.Type, h.ID.Str())
}

// BlobType specifies what a blob stored in a pack is.
type BlobType uint8

// These are the blob types that can be stored in a pack.
const (
InvalidBlob BlobType = iota
DataBlob
TreeBlob
NumBlobTypes // Number of types. Must be last in this enumeration.
)

func (t BlobType) String() string {
switch t {
case DataBlob:
return "data"
case TreeBlob:
return "tree"
case InvalidBlob:
return "invalid"
}

return fmt.Sprintf("<BlobType %d>", t)
}

// MarshalJSON encodes the BlobType into JSON.
func (t BlobType) MarshalJSON() ([]byte, error) {
switch t {
case DataBlob:
return []byte(`"data"`), nil
case TreeBlob:
return []byte(`"tree"`), nil
}

return nil, errors.New("unknown blob type")
}

// UnmarshalJSON decodes the BlobType from JSON.
func (t *BlobType) UnmarshalJSON(buf []byte) error {
switch string(buf) {
case `"data"`:
*t = DataBlob
case `"tree"`:
*t = TreeBlob
default:
return errors.New("unknown blob type")
}

return nil
}

// BlobHandles is an ordered list of BlobHandles that implements sort.Interface.
type BlobHandles []BlobHandle

func (h BlobHandles) Len() int {
return len(h)
}

func (h BlobHandles) Less(i, j int) bool {
for k, b := range h[i].ID {
if b == h[j].ID[k] {
continue
}

if b < h[j].ID[k] {
return true
}

return false
}

return h[i].Type < h[j].Type
}

func (h BlobHandles) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}

func (h BlobHandles) String() string {
elements := make([]string, 0, len(h))
for _, e := range h {
elements = append(elements, e.String())
}
return fmt.Sprintf("%v", elements)
}

// DecryptAndCheck decrypts the blob contents, optionally checking if the content
// is valid.
func (blob *Blob) DecryptAndCheck(reader io.ReaderAt, key *crypto.Key, check bool) ([]byte, error) {
// load blob from pack

buf := make([]byte, blob.Length)

n, err := reader.ReadAt(buf, int64(blob.Offset))
if err != nil {
return nil, err
}

if uint(n) != blob.Length {
return nil, fmt.Errorf("error loading blob %v: wrong length returned, want %d, got %d",
blob.ID.Str(), blob.Length, uint(n))
}

// decrypt
nonce, ciphertext := buf[:key.NonceSize()], buf[key.NonceSize():]
plaintext, err := key.Open(ciphertext[:0], nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("decrypting blob %v failed: %v", blob.ID, err)
}

if check && !restic.Hash(plaintext).Equal(blob.ID) {
return nil, fmt.Errorf("blob %v returned invalid hash", blob.ID)
}

return plaintext, nil
}

// DecryptAndCheck decrypts the blob contents.
//
// Does not check content validity.
func (blob *Blob) Decrypt(reader io.ReaderAt, key *crypto.Key) ([]byte, error) {
return blob.DecryptAndCheck(reader, key, false)
}
109 changes: 109 additions & 0 deletions blob/blob_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package blob

import "sort"

// BlobSet is a set of blobs.
type BlobSet map[BlobHandle]struct{}

// NewBlobSet returns a new BlobSet, populated with ids.
func NewBlobSet(handles ...BlobHandle) BlobSet {
m := make(BlobSet)
for _, h := range handles {
m[h] = struct{}{}
}

return m
}

// Has returns true iff id is contained in the set.
func (s BlobSet) Has(h BlobHandle) bool {
_, ok := s[h]
return ok
}

// Insert adds id to the set.
func (s BlobSet) Insert(h BlobHandle) {
s[h] = struct{}{}
}

// Delete removes id from the set.
func (s BlobSet) Delete(h BlobHandle) {
delete(s, h)
}

// Equals returns true iff s equals other.
func (s BlobSet) Equals(other BlobSet) bool {
if len(s) != len(other) {
return false
}

for h := range s {
if _, ok := other[h]; !ok {
return false
}
}

return true
}

// Merge adds the blobs in other to the current set.
func (s BlobSet) Merge(other BlobSet) {
for h := range other {
s.Insert(h)
}
}

// Intersect returns a new set containing the handles that are present in both sets.
func (s BlobSet) Intersect(other BlobSet) (result BlobSet) {
result = NewBlobSet()

set1 := s
set2 := other

// iterate over the smaller set
if len(set2) < len(set1) {
set1, set2 = set2, set1
}

for h := range set1 {
if set2.Has(h) {
result.Insert(h)
}
}

return result
}

// Sub returns a new set containing all handles that are present in s but not in
// other.
func (s BlobSet) Sub(other BlobSet) (result BlobSet) {
result = NewBlobSet()
for h := range s {
if !other.Has(h) {
result.Insert(h)
}
}

return result
}

// List returns a sorted slice of all BlobHandle in the set.
func (s BlobSet) List() BlobHandles {
list := make(BlobHandles, 0, len(s))
for h := range s {
list = append(list, h)
}

sort.Sort(list)

return list
}

func (s BlobSet) String() string {
str := s.List().String()
if len(str) < 2 {
return "{}"
}

return "{" + str[1:len(str)-1] + "}"
}
41 changes: 41 additions & 0 deletions blob/blob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package blob

import (
"encoding/json"
"testing"
)

var blobTypeJSON = []struct {
t BlobType
res string
}{
{DataBlob, `"data"`},
{TreeBlob, `"tree"`},
}

func TestBlobTypeJSON(t *testing.T) {
for _, test := range blobTypeJSON {
// test serialize
buf, err := json.Marshal(test.t)
if err != nil {
t.Error(err)
continue
}
if test.res != string(buf) {
t.Errorf("want %q, got %q", test.res, string(buf))
continue
}

// test unserialize
var v BlobType
err = json.Unmarshal([]byte(test.res), &v)
if err != nil {
t.Error(err)
continue
}
if test.t != v {
t.Errorf("want %v, got %v", test.t, v)
continue
}
}
}
Loading

0 comments on commit a69fbd8

Please sign in to comment.