/* NAME meta.go DESCRIPTION See Readme.md AUTHOR Saxon Nelson-Milton LICENSE meta.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. */ package meta import ( "encoding/binary" "errors" "strings" "sync" ) // This is the headsize of our metadata string, // which is encoded int the data body of a pmt descriptor. const headSize = 4 const ( majVer = 1 minVer = 0 ) // Indices of bytes for uint16 metadata length. const ( dataLenIdx = 2 ) var ( errKeyAbsent = errors.New("Key does not exist in map") errNoHeader = errors.New("Metadata string does not contain header") errInvalidHeader = errors.New("Metadata string does not contain valid header") errUnexpectedMetaFormat = errors.New("Unexpected meta format") ) // Metadata provides functionality for the storage and encoding of metadata // using a map. type Data struct { mu sync.RWMutex data map[string]string order []string enc []byte } // New returns a pointer to a new Metadata. func New() *Data { return &Data{ data: make(map[string]string), enc: []byte{ 0x00, // Reserved byte (majVer << 4) | minVer, // MS and LS versions 0x00, // Data len byte1 0x00, // Data len byte2 }, } } // NewWith creates a meta.Data and fills map with initial data given. If there // is repeated key, then the latter overwrites the prior. func NewWith(data [][2]string) *Data { m := New() m.order = make([]string, 0, len(data)) for _, d := range data { if _, exists := m.data[d[0]]; !exists { m.order = append(m.order, d[0]) } m.data[d[0]] = d[1] } return m } // Add adds metadata with key and val. func (m *Data) Add(key, val string) { m.mu.Lock() defer m.mu.Unlock() m.data[key] = val for _, k := range m.order { if k == key { return } } m.order = append(m.order, key) return } // All returns the a copy of the map containing the meta data. func (m *Data) All() map[string]string { m.mu.Lock() cpy := make(map[string]string) for k, v := range m.data { cpy[k] = v } m.mu.Unlock() return cpy } // Get returns the meta data for the passed key. func (m *Data) Get(key string) (val string, ok bool) { m.mu.Lock() val, ok = m.data[key] m.mu.Unlock() return } // Delete deletes a meta entry in the map and returns error if it doesn’t exist. func (m *Data) Delete(key string) { m.mu.Lock() defer m.mu.Unlock() if _, ok := m.data[key]; ok { delete(m.data, key) for i, k := range m.order { if k == key { copy(m.order[:i], m.order[i+1:]) m.order = m.order[:len(m.order)-1] break } } return } return } // Encode takes the meta data map and encodes into a byte slice with header // describing the version, length of data and data in TSV format. func (m *Data) Encode() []byte { m.enc = m.enc[:headSize] // Iterate over map and append entries, only adding tab if we're not on the // last entry. var entry string for i, k := range m.order { v := m.data[k] entry += k + "=" + v if i+1 < len(m.data) { entry += "\t" } } m.enc = append(m.enc, []byte(entry)...) // Calculate and set data length in encoded meta header. dataLen := len(m.enc[headSize:]) binary.BigEndian.PutUint16(m.enc[dataLenIdx:dataLenIdx+2], uint16(dataLen)) return m.enc } // ReadFrom gets a value from a metadata string d, for the given key. If the // key is not present in the metadata string, an error is returned. If the // metadata header is not present in the string, an error is returned. func Get(key string, d []byte) (string, error) { err := checkHeader(d) if err != nil { return "", err } d = d[headSize:] entries := strings.Split(string(d), "\t") for _, entry := range entries { kv := strings.Split(entry, "=") if kv[0] == key { return kv[1], nil } } return "", errKeyAbsent } // GetAll gets all metadata entries from given data. An Error is returned // if the metadata does not have a valid header, or if the meta format is unexpected. func GetAll(d []byte) ([][2]string, error) { err := checkHeader(d) if err != nil { return nil, err } d = d[headSize:] entries := strings.Split(string(d), "\t") all := make([][2]string, len(entries)) for i, entry := range entries { kv := strings.Split(entry, "=") if len(kv) != 2 { return nil, errUnexpectedMetaFormat } copy(all[i][:], kv) } return all, nil } // checkHeader checks that a valid metadata header exists in the given data. An // error is returned if the header is absent, or if the header is not valid. func checkHeader(d []byte) error { if d[0] != 0 { return errNoHeader } else if d[0] == 0 && binary.BigEndian.Uint16(d[2:headSize]) != uint16(len(d[headSize:])) { return errInvalidHeader } return nil }