@@ -6,14 +6,19 @@ package tftypes
66import (
77 "bytes"
88 "fmt"
9+ "io"
910 "math"
1011 "math/big"
1112 "sort"
1213
14+ "github.com/hashicorp/terraform-plugin-go/tftypes/refinement"
1315 msgpack "github.com/vmihailenco/msgpack/v5"
1416 msgpackCodes "github.com/vmihailenco/msgpack/v5/msgpcode"
1517)
1618
19+ // https://github.com/zclconf/go-cty/blob/main/cty/msgpack/unknown.go#L32
20+ const unknownWithRefinementsExt = 0x0c
21+
1722type msgPackUnknownType struct {}
1823
1924var msgPackUnknownVal = msgPackUnknownType {}
@@ -43,11 +48,7 @@ func msgpackUnmarshal(dec *msgpack.Decoder, typ Type, path *AttributePath) (Valu
4348 }
4449 if msgpackCodes .IsExt (peek ) {
4550 // as with go-cty, assume all extensions are unknown values
46- err := dec .Skip ()
47- if err != nil {
48- return Value {}, path .NewErrorf ("error skipping extension byte: %w" , err )
49- }
50- return NewValue (typ , UnknownValue ), nil
51+ return msgpackUnmarshalUnknown (dec , typ , path )
5152 }
5253 if typ .Is (DynamicPseudoType ) {
5354 return msgpackUnmarshalDynamic (dec , path )
@@ -344,18 +345,99 @@ func msgpackUnmarshalDynamic(dec *msgpack.Decoder, path *AttributePath) (Value,
344345 }
345346 return msgpackUnmarshal (dec , typ , path )
346347}
348+ func msgpackUnmarshalUnknown (dec * msgpack.Decoder , typ Type , path * AttributePath ) (Value , error ) {
349+ // The value is unknown, but we will check the extension header to see if any
350+ // type-specific refinements are applied to the value.
351+ typeCode , extLen , err := dec .DecodeExtHeader ()
352+ if err != nil {
353+ return Value {}, path .NewErrorf ("error decoding extension header: %w" , err )
354+ }
355+
356+ if extLen <= 1 {
357+ // If the extension is zero or one-length, this is a wholly unknown value with no
358+ // refinements.
359+
360+ // TODO: Previous implementations always skipped the body, should we preserve that?
361+ if extLen > 0 {
362+ // Skip the body
363+ err = dec .Skip ()
364+ if err != nil {
365+ return Value {}, path .NewErrorf ("error skipping extension byte: %w" , err )
366+ }
367+ }
368+
369+ return NewValue (typ , UnknownValue ), nil
370+ }
371+
372+ // Check if the extension is the designated cty unknown refinement code
373+ if typeCode != unknownWithRefinementsExt {
374+ // TODO: cleanup error
375+ return Value {}, path .NewErrorf ("unsupported extension type" )
376+ }
377+
378+ if extLen > 1024 {
379+ // A refinement description greater than 1 kiB is unreasonable and
380+ // might be an abusive attempt to allocate large amounts of memory
381+ // in a system consuming this input.
382+ return Value {}, path .NewErrorf ("unsupported extension type" )
383+ }
384+
385+ body := make ([]byte , extLen )
386+ _ , err = io .ReadAtLeast (dec .Buffered (), body , len (body ))
387+ if err != nil {
388+ return Value {}, path .NewErrorf ("failed to read msgpack extension body: %s" , err )
389+ }
390+
391+ rfnDec := msgpack .NewDecoder (bytes .NewReader (body ))
392+ entryCount , err := rfnDec .DecodeMapLen ()
393+ if err != nil {
394+ return Value {}, path .NewErrorf ("failed to decode msgpack extension body: not a map" )
395+ }
396+
397+ // Ignore all refinements for DynamicPseudoType for now, since go-cty also ignores this.
398+ //
399+ // We know that's invalid today but we might find a way to support it in the future and
400+ // if so will want to introduce that in a backward-compatible way.
401+ if typ .Is (DynamicPseudoType ) {
402+ return NewValue (typ , UnknownValue ), nil
403+ }
404+
405+ newVal := NewValue (typ , UnknownValue )
406+ newRefinements := make (refinement.Refinements , 0 )
407+
408+ for i := 0 ; i < entryCount ; i ++ {
409+ keyCode , err := rfnDec .DecodeInt64 ()
410+ if err != nil {
411+ return Value {}, path .NewErrorf ("failed to decode msgpack extension body: non-integer key in map" )
412+ }
413+
414+ switch keyCode := refinement .Key (keyCode ); keyCode {
415+ case refinement .KeyNullness :
416+ isNull , err := rfnDec .DecodeBool ()
417+ if err != nil {
418+ return Value {}, path .NewErrorf ("failed to decode msgpack extension body: null refinement is not boolean" )
419+ }
420+ // The presence of this key means we're refining the null-ness one
421+ // way or another. If nullness is unknown then this key should not
422+ // be present at all.
423+ newRefinements [keyCode ] = refinement .Nullness (isNull )
424+ default :
425+ // We don't want to error here, as go-cty could introduce new refinements that we'd
426+ // want to just ignore until this code is updated
427+ continue
428+ }
429+ }
430+
431+ return newVal .Refine (newRefinements ), nil
432+ }
347433
348434func marshalMsgPack (val Value , typ Type , p * AttributePath , enc * msgpack.Encoder ) error {
349435 if typ .Is (DynamicPseudoType ) && ! val .Type ().Is (DynamicPseudoType ) {
350436 return marshalMsgPackDynamicPseudoType (val , typ , p , enc )
351437
352438 }
353439 if ! val .IsKnown () {
354- err := enc .Encode (msgPackUnknownVal )
355- if err != nil {
356- return p .NewErrorf ("error encoding UnknownValue: %w" , err )
357- }
358- return nil
440+ return marshalUnknownValue (val , typ , p , enc )
359441 }
360442 if val .IsNull () {
361443 err := enc .EncodeNil ()
@@ -390,6 +472,65 @@ func marshalMsgPack(val Value, typ Type, p *AttributePath, enc *msgpack.Encoder)
390472 return fmt .Errorf ("unknown type %s" , typ )
391473}
392474
475+ func marshalUnknownValue (val Value , typ Type , p * AttributePath , enc * msgpack.Encoder ) error {
476+ // Use the representation of a wholly unknown value if there are no refinements. DynamicPseudoType
477+ // cannot have refinements, so it will also use the wholly unknown value.
478+ if len (val .refinements ) == 0 || typ .Is (DynamicPseudoType ) {
479+ err := enc .Encode (msgPackUnknownVal )
480+ if err != nil {
481+ return p .NewErrorf ("error encoding UnknownValue: %w" , err )
482+ }
483+ return nil
484+ }
485+
486+ var refnBuf bytes.Buffer
487+ refnEnc := msgpack .NewEncoder (& refnBuf )
488+ mapLen := 0
489+
490+ // TODO: Should the refinement interface define the encoding?
491+ for kind := range val .refinements {
492+ switch kind {
493+ case refinement .KeyNullness :
494+ mapLen ++
495+ refnEnc .EncodeInt (int64 (kind )) //nolint
496+ // An value that is definitely null cannot be unknown
497+ refnEnc .EncodeBool (false ) //nolint
498+ default :
499+ continue
500+ }
501+ }
502+
503+ if mapLen == 0 {
504+ // Didn't find any refinements we know how to encode, use the wholly unknown value.
505+ err := enc .Encode (msgPackUnknownVal )
506+ if err != nil {
507+ return p .NewErrorf ("error encoding UnknownValue: %w" , err )
508+ }
509+ return nil
510+ }
511+
512+ // If we have at least one refinement to encode then we'll use the new
513+ // representation of unknown values where refinement information is in the
514+ // extension payload.
515+ var lenBuf bytes.Buffer
516+ lenEnc := msgpack .NewEncoder (& lenBuf )
517+ lenEnc .EncodeMapLen (mapLen ) //nolint
518+
519+ err := enc .EncodeExtHeader (unknownWithRefinementsExt , lenBuf .Len ()+ refnBuf .Len ())
520+ if err != nil {
521+ return p .NewErrorf ("failed to write unknown value: %s" , err )
522+ }
523+ _ , err = enc .Writer ().Write (lenBuf .Bytes ())
524+ if err != nil {
525+ return p .NewErrorf ("failed to write unknown value: %s" , err )
526+ }
527+ _ , err = enc .Writer ().Write (refnBuf .Bytes ())
528+ if err != nil {
529+ return p .NewErrorf ("failed to write unknown value: %s" , err )
530+ }
531+ return nil
532+ }
533+
393534func marshalMsgPackDynamicPseudoType (val Value , _ Type , p * AttributePath , enc * msgpack.Encoder ) error {
394535 typeJSON , err := val .Type ().MarshalJSON ()
395536 if err != nil {
0 commit comments