mirror of
https://github.com/PeernetOfficial/core.git
synced 2026-07-17 02:47:51 +01:00
215 lines
7.2 KiB
Go
215 lines
7.2 KiB
Go
/*
|
|
File Name: Block Encoding.go
|
|
Copyright: 2021 Peernet s.r.o.
|
|
Author: Peter Kleissner
|
|
|
|
Encoding of records inside blocks.
|
|
|
|
File records:
|
|
Offset Size Info
|
|
0 32 Hash blake3 of the file content
|
|
32 1 File type (low-level)
|
|
33 2 File format (high-level)
|
|
35 8 File size
|
|
43 2 Directory ID
|
|
45 2 Size of file name in bytes
|
|
47 2 Count of tags
|
|
49 ? File name
|
|
? ? Tags (TBD)
|
|
|
|
Directory records:
|
|
Offset Size Info
|
|
0 2 Directory ID
|
|
2 ? Directory name
|
|
|
|
*/
|
|
|
|
package core
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
|
|
"github.com/PeernetOfficial/core/sanitize"
|
|
)
|
|
|
|
// BlockDecoded contains the decoded records from a block
|
|
type BlockDecoded struct {
|
|
Block
|
|
Files []BlockRecordFile // Files
|
|
User BlockRecordUser // User details
|
|
directories []BlockRecordDirectory // Internal list of directories for decoding files.
|
|
}
|
|
|
|
// RecordTypeX defines the type of the record
|
|
const (
|
|
RecordTypeUsername = 0 // Username. Arbitrary name defined by the user.
|
|
RecordTypeDirectory = 1 // Directory. Only valid in the context of the current block.
|
|
RecordTypeFile = 2 // File
|
|
RecordTypeContentRating = 3 // Content rating (positive).
|
|
RecordTypeContentReport = 4 // Content report (negative).
|
|
RecordTypeDelete = 5 // Delete previous record.
|
|
)
|
|
|
|
// block record structures
|
|
|
|
// BlockRecordUser specifies user information
|
|
type BlockRecordUser struct {
|
|
Valid bool // Whether the username is supplied
|
|
Name string // Arbitrary name of the user.
|
|
NameS string // Sanitized version of the name.
|
|
}
|
|
|
|
// BlockRecordDirectory is a directory, only valid within the same block.
|
|
type BlockRecordDirectory struct {
|
|
ID uint16 // ID, only valid within the same block
|
|
Name string // Name of the directory. Slashes (both backward and forward) mark subdirectories.
|
|
}
|
|
|
|
// BlockRecordFile is the metadata of a file published on the blockchain
|
|
type BlockRecordFile struct {
|
|
Hash []byte // Hash of the file data
|
|
Type uint8 // Type (low-level)
|
|
Format uint16 // Format (high-level)
|
|
Size uint64 // Size of the file
|
|
Directory string // Directory
|
|
Name string // File name
|
|
directoryID uint16 // Internal directory ID
|
|
// Tags todo
|
|
}
|
|
|
|
// Tag structure to be defined
|
|
|
|
// decodeBlockRecords decodes the raw records in the block and returns a high-level decoded structure
|
|
func decodeBlockRecords(block *Block) (decoded *BlockDecoded, err error) {
|
|
decoded = &BlockDecoded{Block: *block}
|
|
|
|
if decoded.Files, decoded.directories, err = decodeBlockRecordFiles(block.RecordsRaw); err != nil {
|
|
return nil, err
|
|
}
|
|
if user := decodeBlockRecordUser(block.RecordsRaw); user != nil {
|
|
decoded.User = *user
|
|
}
|
|
|
|
return decoded, nil
|
|
}
|
|
|
|
// decodeBlockRecordUser decodes the username, if available. Otherwise return nil.
|
|
func decodeBlockRecordUser(recordsRaw []BlockRecordRaw) (user *BlockRecordUser) {
|
|
for _, record := range recordsRaw {
|
|
if record.Type == RecordTypeUsername {
|
|
user = &BlockRecordUser{Valid: true, Name: string(record.Data), NameS: sanitize.Username(string(record.Data))}
|
|
// continue to seek for an overriding username record, even though that would be stupid within the same block
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// encodeBlockRecordUser encodes the username
|
|
func encodeBlockRecordUser(user BlockRecordUser) (recordsRaw []BlockRecordRaw, err error) {
|
|
if user.Valid {
|
|
recordsRaw = append(recordsRaw, BlockRecordRaw{Type: RecordTypeUsername, Data: []byte(user.Name)})
|
|
}
|
|
|
|
return recordsRaw, nil
|
|
}
|
|
|
|
// decodeBlockRecordFiles decodes only file records. Other records are ignored
|
|
func decodeBlockRecordFiles(recordsRaw []BlockRecordRaw) (files []BlockRecordFile, directories []BlockRecordDirectory, err error) {
|
|
for _, record := range recordsRaw {
|
|
switch record.Type {
|
|
case RecordTypeDirectory:
|
|
if len(record.Data) < 3 {
|
|
return nil, nil, errors.New("decodeBlockRecordFiles directory record invalid size")
|
|
}
|
|
|
|
directory := &BlockRecordDirectory{}
|
|
directory.ID = binary.LittleEndian.Uint16(record.Data[0 : 0+2])
|
|
directory.Name = string(record.Data[2:])
|
|
directories = append(directories, *directory)
|
|
|
|
case RecordTypeFile:
|
|
if len(record.Data) < 49 {
|
|
return nil, nil, errors.New("decodeBlockRecordFiles file record invalid size")
|
|
}
|
|
|
|
file := BlockRecordFile{}
|
|
file.Hash = make([]byte, hashSize)
|
|
copy(file.Hash, record.Data[0:0+hashSize])
|
|
file.Type = record.Data[32]
|
|
file.Format = binary.LittleEndian.Uint16(record.Data[33 : 33+2])
|
|
file.Size = binary.LittleEndian.Uint64(record.Data[35 : 35+8])
|
|
directoryID := binary.LittleEndian.Uint16(record.Data[43 : 43+2])
|
|
filenameSize := binary.LittleEndian.Uint16(record.Data[45 : 45+2])
|
|
//countTags := binary.LittleEndian.Uint16(record.Data[47 : 47+2]) // future implementation of tags
|
|
|
|
if len(record.Data) < 49+int(filenameSize) {
|
|
return nil, nil, errors.New("decodeBlockRecordFiles file record invalid filename size")
|
|
}
|
|
file.Name = string(record.Data[49 : 49+filenameSize])
|
|
|
|
for n := range directories {
|
|
if directories[n].ID == directoryID {
|
|
file.Directory = directories[n].Name
|
|
break
|
|
}
|
|
}
|
|
|
|
files = append(files, file)
|
|
}
|
|
}
|
|
|
|
return files, directories, err
|
|
}
|
|
|
|
// encodeBlockRecordFiles encodes files into the block record data
|
|
// This function should be called grouped with all files in the same directory. The directory name is deduplicated; only unique directory records will be returned.
|
|
func encodeBlockRecordFiles(files []BlockRecordFile) (recordsRaw []BlockRecordRaw, err error) {
|
|
// First the directory records must be declared for any references by files
|
|
nextDirectoryID := uint16(1) // start as 1 to prevent collision with files without explicit directory
|
|
directoryList := make(map[string]int)
|
|
|
|
for n := range files {
|
|
files[n].Directory, files[n].Name = sanitize.Path(files[n].Directory, files[n].Name)
|
|
|
|
if files[n].Directory == "" {
|
|
continue
|
|
}
|
|
|
|
if directoryID, ok := directoryList[files[n].Directory]; ok {
|
|
files[n].directoryID = uint16(directoryID)
|
|
continue
|
|
}
|
|
|
|
// Create the new directory record
|
|
var directoryIDb [2]byte
|
|
binary.LittleEndian.PutUint16(directoryIDb[0:2], nextDirectoryID)
|
|
files[n].directoryID = nextDirectoryID
|
|
nextDirectoryID++
|
|
|
|
recordsRaw = append(recordsRaw, BlockRecordRaw{Type: RecordTypeDirectory, Data: append(directoryIDb[:], []byte(files[n].Directory)...)})
|
|
}
|
|
|
|
for n := range files {
|
|
var data [49]byte
|
|
|
|
if len(files[n].Hash) != hashSize {
|
|
return nil, errors.New("encodeBlockRecords invalid file hash")
|
|
}
|
|
copy(data[0:32], files[n].Hash[0:32])
|
|
|
|
data[32] = files[n].Type
|
|
binary.LittleEndian.PutUint16(data[33:33+2], files[n].Format)
|
|
binary.LittleEndian.PutUint64(data[35:35+8], files[n].Size)
|
|
binary.LittleEndian.PutUint16(data[43:43+2], files[n].directoryID)
|
|
|
|
filenameB := []byte(files[n].Name)
|
|
binary.LittleEndian.PutUint16(data[45:45+2], uint16(len(filenameB)))
|
|
binary.LittleEndian.PutUint16(data[47:47+2], uint16(0)) // Count of Tags (future use)
|
|
|
|
recordsRaw = append(recordsRaw, BlockRecordRaw{Type: RecordTypeFile, Data: append(data[:], filenameB...)})
|
|
}
|
|
|
|
return recordsRaw, nil
|
|
}
|