shared_codecs.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. ////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // DO NOT MODIFY
  4. //
  5. // ┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻
  6. //
  7. //
  8. // This source file was automatically generated by bingen.
  9. //
  10. ////////////////////////////////////////////////////////////////////////////////
  11. package shared
  12. import (
  13. "fmt"
  14. util "github.com/opencost/opencost/core/pkg/util"
  15. "io"
  16. "iter"
  17. "os"
  18. "reflect"
  19. "strings"
  20. "sync"
  21. "unsafe"
  22. )
  23. const (
  24. // GeneratorPackageName is the package the generator is targetting
  25. GeneratorPackageName string = "shared"
  26. )
  27. // BinaryTags represent the formatting tag used for specific optimization features
  28. const (
  29. // BinaryTagStringTable is written and/or read prior to the existence of a string
  30. // table (where each index is encoded as a string entry in the resource
  31. BinaryTagStringTable string = "BGST"
  32. )
  33. const (
  34. // DefaultCodecVersion is used for any resources listed in the Default version set
  35. DefaultCodecVersion uint8 = 1
  36. )
  37. //--------------------------------------------------------------------------
  38. // Configuration
  39. //--------------------------------------------------------------------------
  40. var (
  41. bingenConfigLock sync.RWMutex
  42. bingenConfig *BingenConfiguration = DefaultBingenConfiguration()
  43. )
  44. // BingenConfiguration is used to set any custom configuration in the way files are encoded
  45. // or decoded.
  46. type BingenConfiguration struct {
  47. // FileBackedStringTableEnabled enables the use of file-backed string tables for streaming
  48. // bingen decoding.
  49. FileBackedStringTableEnabled bool
  50. // FileBackedStringTableDir is the directory to write the string table files for reading.
  51. FileBackedStringTableDir string
  52. }
  53. // DefaultBingenConfiguration creates the default implementation of the bingen configuration
  54. // and returns it.
  55. func DefaultBingenConfiguration() *BingenConfiguration {
  56. return &BingenConfiguration{
  57. FileBackedStringTableEnabled: false,
  58. FileBackedStringTableDir: os.TempDir(),
  59. }
  60. }
  61. // ConfigureBingen accepts a new *BingenConfiguration instance which updates the internal decoder
  62. // and encoder behavior.
  63. func ConfigureBingen(config *BingenConfiguration) {
  64. bingenConfigLock.Lock()
  65. defer bingenConfigLock.Unlock()
  66. if config == nil {
  67. config = DefaultBingenConfiguration()
  68. }
  69. bingenConfig = config
  70. }
  71. // IsBingenFileBackedStringTableEnabled accessor for file backed string table configuration
  72. func IsBingenFileBackedStringTableEnabled() bool {
  73. bingenConfigLock.RLock()
  74. defer bingenConfigLock.RUnlock()
  75. return bingenConfig.FileBackedStringTableEnabled
  76. }
  77. // BingenFileBackedStringTableDir returns the directory configured for file backed string tables.
  78. func BingenFileBackedStringTableDir() string {
  79. bingenConfigLock.RLock()
  80. defer bingenConfigLock.RUnlock()
  81. return bingenConfig.FileBackedStringTableDir
  82. }
  83. //--------------------------------------------------------------------------
  84. // Type Map
  85. //--------------------------------------------------------------------------
  86. // Generated type map for resolving interface implementations to
  87. // to concrete types
  88. var typeMap map[string]reflect.Type = map[string]reflect.Type{}
  89. //--------------------------------------------------------------------------
  90. // Type Helpers
  91. //--------------------------------------------------------------------------
  92. // isBinaryTag returns true when the first bytes in the provided binary matches the tag
  93. func isBinaryTag(data []byte, tag string) bool {
  94. if len(data) < len(tag) {
  95. return false
  96. }
  97. return string(data[:len(tag)]) == tag
  98. }
  99. // isReaderBinaryTag is used to peek the header for an io.Reader Buffer
  100. func isReaderBinaryTag(buff *util.Buffer, tag string) bool {
  101. data, err := buff.Peek(len(tag))
  102. if err != nil && err != io.EOF {
  103. panic(fmt.Sprintf("called Peek() on a non buffered reader: %s", err))
  104. }
  105. if len(data) < len(tag) {
  106. return false
  107. }
  108. return string(data[:len(tag)]) == tag
  109. }
  110. // appendBytes combines a and b into a new byte array
  111. func appendBytes(a []byte, b []byte) []byte {
  112. al := len(a)
  113. bl := len(b)
  114. tl := al + bl
  115. // allocate a new byte array for the combined
  116. // use native copy for speedy byte copying
  117. result := make([]byte, tl)
  118. copy(result, a)
  119. copy(result[al:], b)
  120. return result
  121. }
  122. // typeToString determines the basic properties of the type, the qualifier, package path, and
  123. // type name, and returns the qualified type
  124. func typeToString(f interface{}) string {
  125. qual := ""
  126. t := reflect.TypeOf(f)
  127. if t.Kind() == reflect.Ptr {
  128. t = t.Elem()
  129. qual = "*"
  130. }
  131. return fmt.Sprintf("%s%s.%s", qual, t.PkgPath(), t.Name())
  132. }
  133. // resolveType uses the name of a type and returns the package, base type name, and whether
  134. // or not it's a pointer.
  135. func resolveType(t string) (pkg string, name string, isPtr bool) {
  136. isPtr = t[:1] == "*"
  137. if isPtr {
  138. t = t[1:]
  139. }
  140. slashIndex := strings.LastIndex(t, "/")
  141. if slashIndex >= 0 {
  142. t = t[slashIndex+1:]
  143. }
  144. parts := strings.Split(t, ".")
  145. if parts[0] == GeneratorPackageName {
  146. parts[0] = ""
  147. }
  148. pkg = parts[0]
  149. name = parts[1]
  150. return
  151. }
  152. //--------------------------------------------------------------------------
  153. // Stream Helpers
  154. //--------------------------------------------------------------------------
  155. // StreamFactoryFunc is an alias for a func that creates a BingenStream implementation.
  156. type StreamFactoryFunc func(io.Reader) BingenStream
  157. // Generated streamable factory map for finding the specific new stream methods
  158. // by T type
  159. var streamFactoryMap map[reflect.Type]StreamFactoryFunc = map[reflect.Type]StreamFactoryFunc{}
  160. // NewStreamFor accepts an io.Reader, and returns a new BingenStream for the generic T
  161. // type provided _if_ it is a registered bingen type that is annotated as 'streamable'. See
  162. // the streamFactoryMap for generated type listings.
  163. func NewStreamFor[T any](reader io.Reader) (BingenStream, error) {
  164. typeKey := reflect.TypeFor[T]()
  165. factory, ok := streamFactoryMap[typeKey]
  166. if !ok {
  167. return nil, fmt.Errorf("the type: %s is not a registered bingen streamable type", typeKey.Name())
  168. }
  169. return factory(reader), nil
  170. }
  171. // BingenStream is the stream interface for all streamable types
  172. type BingenStream interface {
  173. // Stream returns the iterator which will stream each field of the target type and
  174. // return the field info as well as the value.
  175. Stream() iter.Seq2[BingenFieldInfo, *BingenValue]
  176. // Close will close any dynamic io.Reader used to stream in the fields
  177. Close()
  178. // Error returns an error if one occurred during the process of streaming the type's fields.
  179. // This can be checked after iterating through the Stream().
  180. Error() error
  181. }
  182. // BingenValue contains the value of a field as well as any index/key associated with that value.
  183. type BingenValue struct {
  184. Value any
  185. Index any
  186. }
  187. // IsNil is just a method accessor way to check to see if the value returned was nil
  188. func (bv *BingenValue) IsNil() bool {
  189. return bv == nil
  190. }
  191. // creates a single BingenValue instance without a key or index
  192. func singleV(value any) *BingenValue {
  193. return &BingenValue{
  194. Value: value,
  195. }
  196. }
  197. // creates a pair of key/index and value.
  198. func pairV(index any, value any) *BingenValue {
  199. return &BingenValue{
  200. Value: value,
  201. Index: index,
  202. }
  203. }
  204. // BingenFieldInfo contains the type of the field being streamed as well as the name of the field.
  205. type BingenFieldInfo struct {
  206. Type reflect.Type
  207. Name string
  208. }
  209. //--------------------------------------------------------------------------
  210. // String Table Writer
  211. //--------------------------------------------------------------------------
  212. // StringTableWriter maps strings to specific indices for encoding
  213. type StringTableWriter struct {
  214. l sync.Mutex
  215. indices map[string]int
  216. next int
  217. }
  218. // NewStringTableWriter Creates a new StringTableWriter instance with provided contents
  219. func NewStringTableWriter(contents ...string) *StringTableWriter {
  220. st := &StringTableWriter{
  221. indices: make(map[string]int, len(contents)),
  222. next: len(contents),
  223. }
  224. for i, entry := range contents {
  225. st.indices[entry] = i
  226. }
  227. return st
  228. }
  229. // AddOrGet atomically retrieves a string entry's index if it exist. Otherwise, it will
  230. // add the entry and return the index.
  231. func (st *StringTableWriter) AddOrGet(s string) int {
  232. st.l.Lock()
  233. defer st.l.Unlock()
  234. if ind, ok := st.indices[s]; ok {
  235. return ind
  236. }
  237. current := st.next
  238. st.next++
  239. st.indices[s] = current
  240. return current
  241. }
  242. // ToSlice Converts the contents to a string array for encoding.
  243. func (st *StringTableWriter) ToSlice() []string {
  244. st.l.Lock()
  245. defer st.l.Unlock()
  246. if st.next == 0 {
  247. return []string{}
  248. }
  249. sl := make([]string, st.next)
  250. for s, i := range st.indices {
  251. sl[i] = s
  252. }
  253. return sl
  254. }
  255. // ToBytes Converts the contents to a binary encoded representation
  256. func (st *StringTableWriter) ToBytes() []byte {
  257. buff := util.NewBuffer()
  258. buff.WriteBytes([]byte(BinaryTagStringTable)) // bingen table header
  259. strs := st.ToSlice()
  260. buff.WriteInt(len(strs)) // table length
  261. for _, s := range strs {
  262. buff.WriteString(s)
  263. }
  264. return buff.Bytes()
  265. }
  266. //--------------------------------------------------------------------------
  267. // String Table Reader
  268. //--------------------------------------------------------------------------
  269. // StringTableReader is the interface used to read the string table from the decoding.
  270. type StringTableReader interface {
  271. // At returns the string entry at a specific index, or panics on out of bounds.
  272. At(index int) string
  273. // Len returns the total number of strings loaded in the string table.
  274. Len() int
  275. // Close will clear the loaded table, and drop any external resources used.
  276. Close() error
  277. }
  278. // SliceStringTableReader is a basic pre-loaded []string that provides index-based access.
  279. // The cost of this implementation is holding all strings in memory, which provides faster
  280. // lookup performance for memory usage.
  281. type SliceStringTableReader struct {
  282. table []string
  283. }
  284. // NewSliceStringTableReaderFrom creates a new SliceStringTableReader instance loading
  285. // data directly from the buffer. The buffer's position should start at the table length.
  286. func NewSliceStringTableReaderFrom(buffer *util.Buffer) StringTableReader {
  287. // table length
  288. tl := buffer.ReadInt()
  289. var table []string
  290. if tl > 0 {
  291. table = make([]string, tl)
  292. for i := range tl {
  293. table[i] = buffer.ReadString()
  294. }
  295. }
  296. return &SliceStringTableReader{
  297. table: table,
  298. }
  299. }
  300. // At returns the string entry at a specific index, or panics on out of bounds.
  301. func (sstr *SliceStringTableReader) At(index int) string {
  302. if index < 0 || index >= len(sstr.table) {
  303. panic(fmt.Errorf("%s: string table index out of bounds: %d", GeneratorPackageName, index))
  304. }
  305. return sstr.table[index]
  306. }
  307. // Len returns the total number of strings loaded in the string table.
  308. func (sstr *SliceStringTableReader) Len() int {
  309. if sstr == nil {
  310. return 0
  311. }
  312. return len(sstr.table)
  313. }
  314. // Close for the slice tables just nils out the slice and returns
  315. func (sstr *SliceStringTableReader) Close() error {
  316. sstr.table = nil
  317. return nil
  318. }
  319. // fileStringRef maps a bingen string-table index to a payload stored in a temp file.
  320. type fileStringRef struct {
  321. off int64
  322. length int
  323. }
  324. // FileStringTableReader leverages a local file to write string table data for lookup. On
  325. // memory focused systems, this allows a slower parse with a significant decrease in memory
  326. // usage. This implementation is often pair with streaming readers for high throughput with
  327. // reduced memory usage.
  328. type FileStringTableReader struct {
  329. f *os.File
  330. refs []fileStringRef
  331. }
  332. // NewFileStringTableFromBuffer reads exactly tl length-prefixed (uint16) string payloads from buffer
  333. // and appends each payload to a new temp file. It does not retain full strings in memory.
  334. func NewFileStringTableReaderFrom(buffer *util.Buffer, dir string) StringTableReader {
  335. // helper func to cast a string in-place to a byte slice.
  336. // NOTE: Return value is READ-ONLY. DO NOT MODIFY!
  337. byteSliceFor := func(s string) []byte {
  338. return unsafe.Slice(unsafe.StringData(s), len(s))
  339. }
  340. err := os.MkdirAll(dir, 0755)
  341. if err != nil {
  342. panic(fmt.Errorf("%s: failed to create string table directory: %w", GeneratorPackageName, err))
  343. }
  344. f, err := os.CreateTemp(dir, fmt.Sprintf("%s-bgst-*", GeneratorPackageName))
  345. if err != nil {
  346. panic(fmt.Errorf("%s: failed to create string table file: %w", GeneratorPackageName, err))
  347. }
  348. var writeErr error
  349. defer func() {
  350. if writeErr != nil {
  351. _ = f.Close()
  352. }
  353. }()
  354. // table length
  355. tl := buffer.ReadInt()
  356. var refs []fileStringRef
  357. if tl > 0 {
  358. refs = make([]fileStringRef, tl)
  359. for i := range tl {
  360. payload := byteSliceFor(buffer.ReadString())
  361. var off int64
  362. if len(payload) > 0 {
  363. off, err = f.Seek(0, io.SeekEnd)
  364. if err != nil {
  365. writeErr = fmt.Errorf("%s: failed to seek string table file: %w", GeneratorPackageName, err)
  366. panic(writeErr)
  367. }
  368. if _, err := f.Write(payload); err != nil {
  369. writeErr = fmt.Errorf("%s: failed to write string table entry %d: %w", GeneratorPackageName, i, err)
  370. panic(writeErr)
  371. }
  372. }
  373. refs[i] = fileStringRef{
  374. off: off,
  375. length: len(payload),
  376. }
  377. }
  378. }
  379. return &FileStringTableReader{
  380. f: f,
  381. refs: refs,
  382. }
  383. }
  384. // At returns the string from the internal file using the reference's offset and length.
  385. func (fstr *FileStringTableReader) At(index int) string {
  386. if fstr == nil || fstr.f == nil {
  387. panic(fmt.Errorf("%s: failed to read file string table data", GeneratorPackageName))
  388. }
  389. if index < 0 || index >= len(fstr.refs) {
  390. panic(fmt.Errorf("%s: string table index out of bounds: %d", GeneratorPackageName, index))
  391. }
  392. ref := fstr.refs[index]
  393. if ref.length == 0 {
  394. return ""
  395. }
  396. b := make([]byte, ref.length)
  397. _, err := fstr.f.ReadAt(b, ref.off)
  398. if err != nil {
  399. return ""
  400. }
  401. // cast the allocated bytes to a string in-place, as we
  402. // were the ones that allocated the bytes
  403. return unsafe.String(unsafe.SliceData(b), len(b))
  404. }
  405. // Len returns the total number of strings loaded in the string table.
  406. func (fstr *FileStringTableReader) Len() int {
  407. if fstr == nil {
  408. return 0
  409. }
  410. return len(fstr.refs)
  411. }
  412. // Close for the file string table reader closes the file and deletes it.
  413. func (fstr *FileStringTableReader) Close() error {
  414. if fstr == nil || fstr.f == nil {
  415. return nil
  416. }
  417. path := fstr.f.Name()
  418. err := fstr.f.Close()
  419. fstr.f = nil
  420. fstr.refs = nil
  421. if path != "" {
  422. _ = os.Remove(path)
  423. }
  424. return err
  425. }
  426. //--------------------------------------------------------------------------
  427. // Codec Context
  428. //--------------------------------------------------------------------------
  429. // EncodingContext is a context object passed to the encoders to ensure reuse of buffer
  430. // and table data
  431. type EncodingContext struct {
  432. Buffer *util.Buffer
  433. Table *StringTableWriter
  434. }
  435. // IsStringTable returns true if the table is available
  436. func (ec *EncodingContext) IsStringTable() bool {
  437. return ec.Table != nil
  438. }
  439. // DecodingContext is a context object passed to the decoders to ensure parent objects
  440. // reuse as much data as possible
  441. type DecodingContext struct {
  442. Buffer *util.Buffer
  443. Table StringTableReader
  444. }
  445. // NewDecodingContextFromBytes creates a new DecodingContext instance using an byte slice
  446. func NewDecodingContextFromBytes(data []byte) *DecodingContext {
  447. var table StringTableReader
  448. buff := util.NewBufferFromBytes(data)
  449. // string table header validation
  450. if isBinaryTag(data, BinaryTagStringTable) {
  451. buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
  452. // always use a slice string table with a byte array since the
  453. // data is already in memory
  454. table = NewSliceStringTableReaderFrom(buff)
  455. }
  456. return &DecodingContext{
  457. Buffer: buff,
  458. Table: table,
  459. }
  460. }
  461. // NewDecodingContextFromReader creates a new DecodingContext instance using an io.Reader
  462. // implementation
  463. func NewDecodingContextFromReader(reader io.Reader) *DecodingContext {
  464. var table StringTableReader
  465. buff := util.NewBufferFromReader(reader)
  466. if isReaderBinaryTag(buff, BinaryTagStringTable) {
  467. buff.ReadBytes(len(BinaryTagStringTable)) // strip tag length
  468. // create correct string table implementation
  469. if IsBingenFileBackedStringTableEnabled() {
  470. table = NewFileStringTableReaderFrom(buff, BingenFileBackedStringTableDir())
  471. } else {
  472. table = NewSliceStringTableReaderFrom(buff)
  473. }
  474. }
  475. return &DecodingContext{
  476. Buffer: buff,
  477. Table: table,
  478. }
  479. }
  480. // IsStringTable returns true if the table is available
  481. func (dc *DecodingContext) IsStringTable() bool {
  482. return dc.Table != nil && dc.Table.Len() > 0
  483. }
  484. // Close will ensure that any string table resources and buffer resources are
  485. // cleaned up.
  486. func (dc *DecodingContext) Close() {
  487. if dc.Table != nil {
  488. _ = dc.Table.Close()
  489. dc.Table = nil
  490. }
  491. }
  492. //--------------------------------------------------------------------------
  493. // Binary Codec
  494. //--------------------------------------------------------------------------
  495. // BinEncoder is an encoding interface which defines a context based marshal contract.
  496. type BinEncoder interface {
  497. MarshalBinaryWithContext(*EncodingContext) error
  498. }
  499. // BinDecoder is a decoding interface which defines a context based unmarshal contract.
  500. type BinDecoder interface {
  501. UnmarshalBinaryWithContext(*DecodingContext) error
  502. }