github.com/taubyte/vm-wasm-utils@v1.0.2/binary/decoder.go (about)

     1  package binary
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  
     9  	wasm "github.com/taubyte/vm-wasm-utils"
    10  	"github.com/taubyte/vm-wasm-utils/leb128"
    11  )
    12  
    13  // DecodeModule implements wasm.DecodeModule for the WebAssembly 1.0 (20191205) Binary Format
    14  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
    15  func DecodeModule(
    16  	binary []byte,
    17  	enabledFeatures wasm.Features,
    18  	memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32),
    19  ) (*wasm.Module, error) {
    20  	r := bytes.NewReader(binary)
    21  
    22  	// Magic number.
    23  	buf := make([]byte, 4)
    24  	if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, Magic) {
    25  		return nil, ErrInvalidMagicNumber
    26  	}
    27  
    28  	// Version.
    29  	if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, version) {
    30  		return nil, ErrInvalidVersion
    31  	}
    32  
    33  	m := &wasm.Module{}
    34  	for {
    35  		// TODO: except custom sections, all others are required to be in order, but we aren't checking yet.
    36  		// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA
    37  		sectionID, err := r.ReadByte()
    38  		if err == io.EOF {
    39  			break
    40  		} else if err != nil {
    41  			return nil, fmt.Errorf("read section id: %w", err)
    42  		}
    43  
    44  		sectionSize, _, err := leb128.DecodeUint32(r)
    45  		if err != nil {
    46  			return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err)
    47  		}
    48  
    49  		sectionContentStart := r.Len()
    50  		switch sectionID {
    51  		case wasm.SectionIDCustom:
    52  			// First, validate the section and determine if the section for this name has already been set
    53  			name, nameSize, decodeErr := decodeUTF8(r, "custom section name")
    54  			if decodeErr != nil {
    55  				err = decodeErr
    56  				break
    57  			} else if sectionSize < nameSize {
    58  				err = fmt.Errorf("malformed custom section %s", name)
    59  				break
    60  			} else if name == "name" && m.NameSection != nil {
    61  				err = fmt.Errorf("redundant custom section %s", name)
    62  				break
    63  			}
    64  
    65  			// Now, either decode the NameSection or skip an unsupported one
    66  			limit := sectionSize - nameSize
    67  			if name == "name" {
    68  				m.NameSection, err = decodeNameSection(r, uint64(limit))
    69  			} else {
    70  				// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
    71  				if _, err = io.CopyN(io.Discard, r, int64(limit)); err != nil {
    72  					return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err)
    73  				}
    74  			}
    75  
    76  		case wasm.SectionIDType:
    77  			m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
    78  		case wasm.SectionIDImport:
    79  			if m.ImportSection, err = decodeImportSection(r, memorySizer, enabledFeatures); err != nil {
    80  				return nil, err // avoid re-wrapping the error.
    81  			}
    82  		case wasm.SectionIDFunction:
    83  			m.FunctionSection, err = decodeFunctionSection(r)
    84  		case wasm.SectionIDTable:
    85  			m.TableSection, err = decodeTableSection(r, enabledFeatures)
    86  		case wasm.SectionIDMemory:
    87  			m.MemorySection, err = decodeMemorySection(r, memorySizer)
    88  		case wasm.SectionIDGlobal:
    89  			if m.GlobalSection, err = decodeGlobalSection(r, enabledFeatures); err != nil {
    90  				return nil, err // avoid re-wrapping the error.
    91  			}
    92  		case wasm.SectionIDExport:
    93  			m.ExportSection, err = decodeExportSection(r)
    94  		case wasm.SectionIDStart:
    95  			if m.StartSection != nil {
    96  				return nil, errors.New("multiple start sections are invalid")
    97  			}
    98  			m.StartSection, err = decodeStartSection(r)
    99  		case wasm.SectionIDElement:
   100  			m.ElementSection, err = decodeElementSection(r, enabledFeatures)
   101  		case wasm.SectionIDCode:
   102  			m.CodeSection, err = decodeCodeSection(r)
   103  		case wasm.SectionIDData:
   104  			m.DataSection, err = decodeDataSection(r, enabledFeatures)
   105  		case wasm.SectionIDDataCount:
   106  			if err := enabledFeatures.Require(wasm.FeatureBulkMemoryOperations); err != nil {
   107  				return nil, fmt.Errorf("data count section not supported as %v", err)
   108  			}
   109  			m.DataCountSection, err = decodeDataCountSection(r)
   110  		default:
   111  			err = ErrInvalidSectionID
   112  		}
   113  
   114  		readBytes := sectionContentStart - r.Len()
   115  		if err == nil && int(sectionSize) != readBytes {
   116  			err = fmt.Errorf("invalid section length: expected to be %d but got %d", sectionSize, readBytes)
   117  		}
   118  
   119  		if err != nil {
   120  			return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err)
   121  		}
   122  	}
   123  
   124  	functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode)
   125  	if functionCount != codeCount {
   126  		return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount)
   127  	}
   128  	return m, nil
   129  }