github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/msgpack/decoder_test.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE
    20  
    21  package msgpack
    22  
    23  import (
    24  	"testing"
    25  
    26  	"github.com/m3db/m3/src/dbnode/digest"
    27  	"github.com/stretchr/testify/require"
    28  )
    29  
    30  func testGenEncodeNumObjectFieldsForFn(
    31  	enc *Encoder,
    32  	targetType objectType,
    33  	delta int,
    34  ) encodeNumObjectFieldsForFn {
    35  	return func(objType objectType) {
    36  		if objType == targetType {
    37  			_, curr := numFieldsForType(objType)
    38  			enc.encodeArrayLenFn(curr + delta)
    39  			return
    40  		}
    41  		enc.encodeNumObjectFieldsFor(objType)
    42  	}
    43  }
    44  
    45  func TestDecodeNewerVersionThanExpected(t *testing.T) {
    46  	var (
    47  		enc = NewEncoder()
    48  		dec = NewDecoder(nil)
    49  	)
    50  
    51  	// Intentionally bump client-side version
    52  	enc.encodeVersionFn = func(version int) {
    53  		enc.encodeVersion(version + 1)
    54  	}
    55  
    56  	// Verify decoding index info results in an error
    57  	require.NoError(t, enc.EncodeIndexInfo(testIndexInfo))
    58  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
    59  	_, err := dec.DecodeIndexInfo()
    60  	require.Error(t, err)
    61  
    62  	// Verify decoding index entry results in an error
    63  	require.NoError(t, enc.EncodeIndexEntry(testIndexEntry))
    64  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
    65  	_, err = dec.DecodeIndexEntry(nil)
    66  	require.Error(t, err)
    67  
    68  	// Verify decoding log info results in an error
    69  	require.NoError(t, enc.EncodeLogInfo(testLogInfo))
    70  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
    71  	_, err = dec.DecodeLogInfo()
    72  	require.Error(t, err)
    73  
    74  	// Verify decoding log entry results in an error
    75  	require.NoError(t, enc.EncodeLogEntry(testLogEntry))
    76  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
    77  	_, err = dec.DecodeLogEntry()
    78  	require.Error(t, err)
    79  
    80  	// Verify decoding log metadata results in an error
    81  	require.NoError(t, enc.EncodeLogMetadata(testLogMetadata))
    82  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
    83  	_, err = dec.DecodeLogMetadata()
    84  	require.Error(t, err)
    85  }
    86  
    87  func TestDecodeRootObjectMoreFieldsThanExpected(t *testing.T) {
    88  	var (
    89  		enc = NewEncoder()
    90  		dec = NewDecoder(nil)
    91  	)
    92  
    93  	// Intentionally bump number of fields for the root object
    94  	enc.encodeNumObjectFieldsForFn = testGenEncodeNumObjectFieldsForFn(enc, rootObjectType, 1)
    95  	require.NoError(t, enc.EncodeIndexInfo(testIndexInfo))
    96  	require.NoError(t, enc.enc.EncodeInt64(1234))
    97  
    98  	// Verify we can successfully skip unnecessary fields
    99  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   100  	res, err := dec.DecodeIndexInfo()
   101  	require.NoError(t, err)
   102  	require.Equal(t, testIndexInfo, res)
   103  }
   104  
   105  func TestDecodeIndexInfoMoreFieldsThanExpected(t *testing.T) {
   106  	var (
   107  		enc = NewEncoder()
   108  		dec = NewDecoder(nil)
   109  	)
   110  
   111  	// Intentionally bump number of fields for the index info object
   112  	enc.encodeNumObjectFieldsForFn = testGenEncodeNumObjectFieldsForFn(enc, indexInfoType, 1)
   113  	require.NoError(t, enc.EncodeIndexInfo(testIndexInfo))
   114  	require.NoError(t, enc.enc.EncodeInt64(1234))
   115  
   116  	// Verify we can successfully skip unnecessary fields
   117  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   118  	res, err := dec.DecodeIndexInfo()
   119  	require.NoError(t, err)
   120  	require.Equal(t, testIndexInfo, res)
   121  }
   122  
   123  func TestDecodeIndexEntryMoreFieldsThanExpected(t *testing.T) {
   124  	var (
   125  		enc = NewEncoder()
   126  		dec = NewDecoder(nil)
   127  	)
   128  
   129  	// Intentionally bump number of fields for the index entry object
   130  	enc.encodeNumObjectFieldsForFn = testGenEncodeNumObjectFieldsForFn(enc, indexEntryType, 1)
   131  	require.NoError(t, enc.EncodeIndexEntry(testIndexEntry))
   132  
   133  	// This hokey bit of logic is done so we can add extra fields in the correct location (since new IndexEntry fields
   134  	// will be added *before* the checksum). Confirm current checksum is correct, strip it, add unexpected field,
   135  	// and re-add updated checksum value
   136  
   137  	// Validate existing checksum
   138  	checksumPos := len(enc.Bytes()) - 5 // 5 bytes = 1 byte for integer code + 4 bytes for checksum
   139  	dec.Reset(NewByteDecoderStream(enc.Bytes()[checksumPos:]))
   140  	currChecksum := dec.decodeVarint()
   141  	require.Equal(t, currChecksum, int64(digest.Checksum(enc.Bytes()[:checksumPos])))
   142  
   143  	// Strip checksum, add new field, add updated checksum
   144  	enc.buf.Truncate(len(enc.Bytes()) - 5)
   145  	require.NoError(t, enc.enc.EncodeInt64(1234))
   146  	checksum := int64(digest.Checksum(enc.Bytes()))
   147  	require.NoError(t, enc.enc.EncodeInt64(checksum))
   148  	expected := testIndexEntry
   149  	expected.IndexChecksum = checksum
   150  
   151  	// Verify we can successfully skip unnecessary fields
   152  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   153  	res, err := dec.DecodeIndexEntry(nil)
   154  	require.NoError(t, err)
   155  	require.Equal(t, expected, res)
   156  }
   157  
   158  func TestDecodeLogInfoMoreFieldsThanExpected(t *testing.T) {
   159  	var (
   160  		enc = NewEncoder()
   161  		dec = NewDecoder(nil)
   162  	)
   163  
   164  	// Intentionally bump number of fields for the log info object
   165  	enc.encodeNumObjectFieldsForFn = testGenEncodeNumObjectFieldsForFn(enc, logInfoType, 1)
   166  	require.NoError(t, enc.EncodeLogInfo(testLogInfo))
   167  	require.NoError(t, enc.enc.EncodeInt64(1234))
   168  
   169  	// Verify we can successfully skip unnecessary fields
   170  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   171  	res, err := dec.DecodeLogInfo()
   172  	require.NoError(t, err)
   173  	require.Equal(t, testLogInfo, res)
   174  }
   175  
   176  func TestDecodeLogEntryMoreFieldsThanExpected(t *testing.T) {
   177  	var (
   178  		enc = NewEncoder()
   179  		dec = NewDecoder(nil)
   180  	)
   181  
   182  	// Intentionally bump number of fields for the log entry object
   183  	enc.encodeNumObjectFieldsForFn = testGenEncodeNumObjectFieldsForFn(enc, logEntryType, 1)
   184  	require.NoError(t, enc.EncodeLogEntry(testLogEntry))
   185  	require.NoError(t, enc.enc.EncodeInt64(1234))
   186  
   187  	// Verify we can successfully skip unnecessary fields
   188  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   189  	res, err := dec.DecodeLogEntry()
   190  	require.NoError(t, err)
   191  	require.Equal(t, testLogEntry, res)
   192  }
   193  
   194  func TestDecodeLogMetadataMoreFieldsThanExpected(t *testing.T) {
   195  	var (
   196  		enc = NewEncoder()
   197  		dec = NewDecoder(nil)
   198  	)
   199  
   200  	// Intentionally bump number of fields for the log metadata object
   201  	enc.encodeNumObjectFieldsForFn = testGenEncodeNumObjectFieldsForFn(enc, logMetadataType, 1)
   202  	require.NoError(t, enc.EncodeLogMetadata(testLogMetadata))
   203  	require.NoError(t, enc.enc.EncodeInt64(1234))
   204  
   205  	// Verify we can successfully skip unnecessary fields
   206  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   207  	res, err := dec.DecodeLogMetadata()
   208  	require.NoError(t, err)
   209  	require.Equal(t, testLogMetadata, res)
   210  }
   211  
   212  func TestDecodeLogEntryFewerFieldsThanExpected(t *testing.T) {
   213  	var (
   214  		enc = NewEncoder()
   215  		dec = NewDecoder(nil)
   216  	)
   217  
   218  	// Intentionally bump number of fields for the log entry object
   219  	enc.encodeNumObjectFieldsForFn = testGenEncodeNumObjectFieldsForFn(enc, logEntryType, -1)
   220  	require.NoError(t, enc.EncodeLogEntry(testLogEntry))
   221  
   222  	// Verify we can successfully skip unnecessary fields
   223  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   224  	_, err := dec.DecodeLogEntry()
   225  	require.Error(t, err)
   226  }
   227  
   228  func TestDecodeBytesNoAlloc(t *testing.T) {
   229  	var (
   230  		enc = NewEncoder()
   231  		dec = NewDecoder(nil)
   232  	)
   233  
   234  	require.NoError(t, enc.EncodeIndexEntry(testIndexEntry))
   235  	data := enc.Bytes()
   236  	dec.Reset(NewByteDecoderStream(data))
   237  	res, err := dec.DecodeIndexEntry(nil)
   238  	require.NoError(t, err)
   239  	require.Equal(t, []byte("testIndexEntry"), res.ID)
   240  
   241  	// Verify ID points to part of encoded data stream
   242  	for i := 0; i < len(data); i++ {
   243  		data[i] = byte('a')
   244  	}
   245  	require.Equal(t, []byte("aaaaaaaaaaaaaa"), res.ID)
   246  }
   247  
   248  func TestDecodeBytesAllocNew(t *testing.T) {
   249  	var (
   250  		enc = NewEncoder()
   251  		dec = NewDecoder(NewDecodingOptions().SetAllocDecodedBytes(true))
   252  	)
   253  
   254  	require.NoError(t, enc.EncodeIndexEntry(testIndexEntry))
   255  	data := enc.Bytes()
   256  	dec.Reset(NewByteDecoderStream(data))
   257  	res, err := dec.DecodeIndexEntry(nil)
   258  	require.NoError(t, err)
   259  	require.Equal(t, []byte("testIndexEntry"), res.ID)
   260  
   261  	// Verify ID is not part of the encoded byte stream
   262  	for i := 0; i < len(data); i++ {
   263  		data[i] = byte('a')
   264  	}
   265  	require.Equal(t, []byte("testIndexEntry"), res.ID)
   266  }
   267  
   268  func TestDecodeIndexEntryInvalidWideEntry(t *testing.T) {
   269  	var (
   270  		enc = NewEncoder()
   271  		dec = NewDecoder(nil)
   272  	)
   273  	require.NoError(t, enc.EncodeIndexEntry(testIndexEntry))
   274  
   275  	// Update to invalid checksum
   276  	enc.buf.Truncate(len(enc.Bytes()) - 5) // 5 bytes = 1 byte for integer code + 4 bytes for checksum
   277  	require.NoError(t, enc.enc.EncodeInt64(1234))
   278  
   279  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   280  	_, err := dec.DecodeIndexEntry(nil)
   281  	require.EqualError(t, err, errorIndexEntryChecksumMismatch.Error())
   282  }
   283  
   284  func TestDecodeIndexEntryIncompleteFile(t *testing.T) {
   285  	var (
   286  		enc = NewEncoder()
   287  		dec = NewDecoder(nil)
   288  	)
   289  	require.NoError(t, enc.EncodeIndexEntry(testIndexEntry))
   290  
   291  	enc.buf.Truncate(len(enc.Bytes()) - 4)
   292  
   293  	dec.Reset(NewByteDecoderStream(enc.Bytes()))
   294  	_, err := dec.DecodeIndexEntry(nil)
   295  	require.EqualError(t, err, "decode index entry encountered error: EOF")
   296  }