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 }