diff --git a/linking/cid/linksystem.go b/linking/cid/linksystem.go index 2c5a996a691b3ea32757f5c62da6939a112f3974..e52c149e935792edf7d6b65ab02a7ffefa4a2f46 100644 --- a/linking/cid/linksystem.go +++ b/linking/cid/linksystem.go @@ -10,12 +10,28 @@ import ( "github.com/ipld/go-ipld-prime/multicodec" ) +// DefaultLinkSystem returns an ipld.LinkSystem which uses cidlink.Link for ipld.Link. +// During selection of encoders, decoders, and hashers, it examines the multicodec indicator numbers and multihash indicator numbers from the CID, +// and uses the default global multicodec registry (see the go-ipld-prime/multicodec package) for resolving codec implementations, +// and the default global multihash registry (see the go-multihash/core package) for resolving multihash implementations. +// +// No storage functions are present in the returned LinkSystem. +// The caller can assign those themselves as desired. func DefaultLinkSystem() ipld.LinkSystem { + return LinkSystemUsingMulticodecRegistry(multicodec.DefaultRegistry) +} + +// LinkSystemUsingMulticodecRegistry is similar to DefaultLinkSystem, but accepts a multicodec.Registry as a parameter. +// +// This can help create a LinkSystem which uses different multicodec implementations than the global registry. +// (Sometimes this can be desired if you want some parts of a program to support a more limited suite of codecs than other parts of the program, +// or needed to use a different multicodec registry than the global one for synchronization purposes, or etc.) +func LinkSystemUsingMulticodecRegistry(mcReg multicodec.Registry) ipld.LinkSystem { return ipld.LinkSystem{ EncoderChooser: func(lp ipld.LinkPrototype) (ipld.Encoder, error) { switch lp2 := lp.(type) { case LinkPrototype: - fn, err := multicodec.LookupEncoder(lp2.GetCodec()) + fn, err := mcReg.LookupEncoder(lp2.GetCodec()) if err != nil { return nil, err } @@ -28,7 +44,7 @@ func DefaultLinkSystem() ipld.LinkSystem { lp := lnk.Prototype() switch lp2 := lp.(type) { case LinkPrototype: - fn, err := multicodec.LookupDecoder(lp2.GetCodec()) + fn, err := mcReg.LookupDecoder(lp2.GetCodec()) if err != nil { return nil, err } diff --git a/multicodec/defaultRegistry.go b/multicodec/defaultRegistry.go new file mode 100644 index 0000000000000000000000000000000000000000..00868aa5cd95f632476f3490694799a6b8f3e25c --- /dev/null +++ b/multicodec/defaultRegistry.go @@ -0,0 +1,85 @@ +package multicodec + +import ( + "github.com/ipld/go-ipld-prime" +) + +// DefaultRegistry is a multicodec.Registry instance which is global to the program, +// and is used as a default set of codecs. +// +// Some systems (for example, cidlink.DefaultLinkSystem) will use this default registry, +// which makes it easier to write programs that pass fewer explicit arguments around. +// However, these are *only* for default behaviors; +// variations of functions which allow explicit non-default options should always be available +// (for example, cidlink also has other LinkSystem constructor functions which accept an explicit multicodec.Registry, +// and the LookupEncoder and LookupDecoder functions in any LinkSystem can be replaced). +// +// Since this registry is global, mind that there are also some necessary tradeoffs and limitations: +// It can be difficult to control exactly what's present in this global registry +// (Libraries may register codecs in this registry as a side-effect of importing, so even transitive dependencies can affect its content!). +// Also, this registry is only considered safe to modify at package init time. +// If these are concerns for your program, you can create your own multicodec.Registry values, +// and eschew using the global default. +var DefaultRegistry = Registry{} + +// RegisterEncoder updates the global DefaultRegistry to map a multicodec indicator number to the given ipld.Encoder function. +// The encoder functions registered can be subsequently looked up using LookupEncoder. +// It is a shortcut to the RegisterEncoder method on the global DefaultRegistry. +// +// Packages which implement an IPLD codec and have a multicodec number associated with them +// are encouraged to register themselves at package init time using this function. +// (Doing this at package init time ensures the default global registry is populated +// without causing race conditions for application code.) +// +// No effort is made to detect conflicting registrations in this map. +// If your dependency tree is such that this becomes a problem, +// there are two ways to address this: +// If RegisterEncoder is called with the same indicator code more than once, the last call wins. +// In practice, this means that if an application has a strong opinion about what implementation for a certain codec, +// then this can be done by making a Register call with that effect at init time in the application's main package. +// This should have the desired effect because the root of the import tree has its init time effect last. +// Alternatively, one can just avoid use of this registry entirely: +// do this by making a LinkSystem that uses a custom EncoderChooser function. +func RegisterEncoder(indicator uint64, encodeFunc ipld.Encoder) { + DefaultRegistry.RegisterEncoder(indicator, encodeFunc) +} + +// LookupEncoder yields an ipld.Encoder function matching a multicodec indicator code number. +// It is a shortcut to the LookupEncoder method on the global DefaultRegistry. +// +// To be available from this lookup function, an encoder must have been registered +// for this indicator number by an earlier call to the RegisterEncoder function. +func LookupEncoder(indicator uint64) (ipld.Encoder, error) { + return DefaultRegistry.LookupEncoder(indicator) +} + +// RegisterDecoder updates the global DefaultRegistry a map a multicodec indicator number to the given ipld.Decoder function. +// The decoder functions registered can be subsequently looked up using LookupDecoder. +// It is a shortcut to the RegisterDecoder method on the global DefaultRegistry. +// +// Packages which implement an IPLD codec and have a multicodec number associated with them +// are encouraged to register themselves in this map at package init time. +// (Doing this at package init time ensures the default global registry is populated +// without causing race conditions for application code.) +// +// No effort is made to detect conflicting registrations in this map. +// If your dependency tree is such that this becomes a problem, +// there are two ways to address this: +// If RegisterDecoder is called with the same indicator code more than once, the last call wins. +// In practice, this means that if an application has a strong opinion about what implementation for a certain codec, +// then this can be done by making a Register call with that effect at init time in the application's main package. +// This should have the desired effect because the root of the import tree has its init time effect last. +// Alternatively, one can just avoid use of this registry entirely: +// do this by making a LinkSystem that uses a custom DecoderChooser function. +func RegisterDecoder(indicator uint64, decodeFunc ipld.Decoder) { + DefaultRegistry.RegisterDecoder(indicator, decodeFunc) +} + +// LookupDecoder yields an ipld.Decoder function matching a multicodec indicator code number. +// It is a shortcut to the LookupDecoder method on the global DefaultRegistry. +// +// To be available from this lookup function, an decoder must have been registered +// for this indicator number by an earlier call to the RegisterDecoder function. +func LookupDecoder(indicator uint64) (ipld.Decoder, error) { + return DefaultRegistry.LookupDecoder(indicator) +} diff --git a/multicodec/multicodec.go b/multicodec/multicodec.go deleted file mode 100644 index 2898834d82d04a8d0249787247b21d71cb134b48..0000000000000000000000000000000000000000 --- a/multicodec/multicodec.go +++ /dev/null @@ -1,110 +0,0 @@ -package multicodec - -import ( - "fmt" - - "github.com/ipld/go-ipld-prime" -) - -var encoderRegistry = make(map[uint64]ipld.Encoder) -var decoderRegistry = make(map[uint64]ipld.Decoder) - -// RegisterEncoder updates a simple map of multicodec indicator number to ipld.Encoder function. -// The encoder functions registered can be subsequently looked up using LookupEncoder. -// -// Packages which implement an IPLD codec and have a multicodec number reserved in -// https://github.com/multiformats/multicodec/blob/master/table.csv -// are encouraged to register themselves in this map at package init time. -// (Doing this at package init time ensures this map can be accessed without race conditions.) -// -// This registry map is only used for default behaviors. -// For example, linking/cid.DefaultLinkSystem will use LookupEncoder to access this registry map -// and select encoders to use when serializing data for linking and storage. -// LinkSystem itself is not hardcoded to use the global LookupEncoder feature; -// therefore, you don't want to rely on this mapping, you can always construct your own LinkSystem. -// -// No effort is made to detect conflicting registrations in this map. -// If your dependency tree is such that this becomes a problem, -// there are two ways to address this: -// If RegisterEncoder is called with the same indicator code more than once, the last call wins. -// In practice, this means that if an application has a strong opinion about what implementation for a certain codec, -// then this can be done by making a Register call with that effect at init time in the application's main package. -// This should have the desired effect because the root of the import tree has its init time effect last. -// Alternatively, one can just avoid use of this registry entirely: -// do this by making a LinkSystem that uses a custom EncoderChooser function. -func RegisterEncoder(indicator uint64, encodeFunc ipld.Encoder) { - // This function could arguably be just a bare map access. - // We introduced a function primarily for the interest of potential future changes. - // E.g. one could introduce logging here to help detect unintended conflicting registrations. - // (We probably won't do this, but you can do it yourself as a printf debug hack. :)) - - if encodeFunc == nil { - panic("not sensible to attempt to register a nil function") - } - encoderRegistry[indicator] = encodeFunc -} - -// LookupEncoder yields an ipld.Encoder function matching a multicodec indicator code number. -// -// Multicodec indicator numbers are specified in -// https://github.com/multiformats/multicodec/blob/master/table.csv -// -// To be available from this lookup function, an encoder must have been registered -// for this indicator number by an earlier call to the RegisterEncoder function. -func LookupEncoder(indicator uint64) (ipld.Encoder, error) { - encodeFunc, exists := encoderRegistry[indicator] - if !exists { - return nil, fmt.Errorf("no encoder registered for multicodec code %d (0x%x)", indicator, indicator) - } - return encodeFunc, nil -} - -// RegisterDecoder updates a simple map of multicodec indicator number to ipld.Decoder function. -// The decoder functions registered can be subsequently looked up using LookupDecoder. -// -// Packages which implement an IPLD codec and have a multicodec number reserved in -// https://github.com/multiformats/multicodec/blob/master/table.csv -// are encouraged to register themselves in this map at package init time. -// (Doing this at package init time ensures this map can be accessed without race conditions.) -// -// This registry map is only used for default behaviors. -// For example, linking/cid.DefaultLinkSystem will use LookupDecoder to access this registry map -// and select decoders to use when serializing data for linking and storage. -// LinkSystem itself is not hardcoded to use the global LookupDecoder feature; -// therefore, you don't want to rely on this mapping, you can always construct your own LinkSystem. -// -// No effort is made to detect conflicting registrations in this map. -// If your dependency tree is such that this becomes a problem, -// there are two ways to address this: -// If RegisterDecoder is called with the same indicator code more than once, the last call wins. -// In practice, this means that if an application has a strong opinion about what implementation for a certain codec, -// then this can be done by making a Register call with that effect at init time in the application's main package. -// This should have the desired effect because the root of the import tree has its init time effect last. -// Alternatively, one can just avoid use of this registry entirely: -// do this by making a LinkSystem that uses a custom DecoderChooser function. -func RegisterDecoder(indicator uint64, decodeFunc ipld.Decoder) { - // This function could arguably be just a bare map access. - // We introduced a function primarily for the interest of potential future changes. - // E.g. one could introduce logging here to help detect unintended conflicting registrations. - // (We probably won't do this, but you can do it yourself as a printf debug hack. :)) - - if decodeFunc == nil { - panic("not sensible to attempt to register a nil function") - } - decoderRegistry[indicator] = decodeFunc -} - -// LookupDecoder yields an ipld.Decoder function matching a multicodec indicator code number. -// -// Multicodec indicator numbers are specified in -// https://github.com/multiformats/multicodec/blob/master/table.csv -// -// To be available from this lookup function, an decoder must have been registered -// for this indicator number by an earlier call to the RegisterDecoder function. -func LookupDecoder(indicator uint64) (ipld.Decoder, error) { - decodeFunc, exists := decoderRegistry[indicator] - if !exists { - return nil, fmt.Errorf("no decoder registered for multicodec code %d (0x%x)", indicator, indicator) - } - return decodeFunc, nil -} diff --git a/multicodec/registry.go b/multicodec/registry.go new file mode 100644 index 0000000000000000000000000000000000000000..fe7d608b1fd9e600c38f0211fc89d0eb359c94cd --- /dev/null +++ b/multicodec/registry.go @@ -0,0 +1,83 @@ +package multicodec + +import ( + "fmt" + + "github.com/ipld/go-ipld-prime" +) + +// Registry is a structure for storing mappings of multicodec indicator numbers to ipld.Encoder and ipld.Decoder functions. +// +// The most typical usage of this structure is in combination with an ipld.LinkSystem. +// For example, a linksystem using CIDs and a custom multicodec registry can be constructed +// using cidlink.LinkSystemUsingMulticodecRegistry. +// +// Registry includes no mutexing. If using Registry in a concurrent context, you must handle synchronization yourself. +// (Typically, it is recommended to do initialization earlier in a program, before fanning out goroutines; +// this avoids the need for mutexing overhead.) +// +// go-ipld-prime also has a default registry, which has the same methods as this structure, but are at package scope. +// Some systems, like cidlink.DefaultLinkSystem, will use this default registry. +// However, this default registry is global to the entire program. +// This Registry type is for helping if you wish to make your own registry which does not share that global state. +// +// Multicodec indicator numbers are specified in +// https://github.com/multiformats/multicodec/blob/master/table.csv . +// You should not use indicator numbers which are not specified in that table +// (however, there is nothing in this implementation that will attempt to stop you, either; please behave). +type Registry struct { + encoders map[uint64]ipld.Encoder + decoders map[uint64]ipld.Decoder +} + +func (r *Registry) ensureInit() { + if r.encoders != nil { + return + } + r.encoders = make(map[uint64]ipld.Encoder) + r.decoders = make(map[uint64]ipld.Decoder) +} + +// RegisterEncoder updates a simple map of multicodec indicator number to ipld.Encoder function. +// The encoder functions registered can be subsequently looked up using LookupEncoder. +func (r *Registry) RegisterEncoder(indicator uint64, encodeFunc ipld.Encoder) { + r.ensureInit() + if encodeFunc == nil { + panic("not sensible to attempt to register a nil function") + } + r.encoders[indicator] = encodeFunc +} + +// LookupEncoder yields an ipld.Encoder function matching a multicodec indicator code number. +// +// To be available from this lookup function, an encoder must have been registered +// for this indicator number by an earlier call to the RegisterEncoder function. +func (r *Registry) LookupEncoder(indicator uint64) (ipld.Encoder, error) { + encodeFunc, exists := r.encoders[indicator] + if !exists { + return nil, fmt.Errorf("no encoder registered for multicodec code %d (0x%x)", indicator, indicator) + } + return encodeFunc, nil +} + +// RegisterDecoder updates a simple map of multicodec indicator number to ipld.Decoder function. +// The decoder functions registered can be subsequently looked up using LookupDecoder. +func (r *Registry) RegisterDecoder(indicator uint64, decodeFunc ipld.Decoder) { + r.ensureInit() + if decodeFunc == nil { + panic("not sensible to attempt to register a nil function") + } + r.decoders[indicator] = decodeFunc +} + +// LookupDecoder yields an ipld.Decoder function matching a multicodec indicator code number. +// +// To be available from this lookup function, an decoder must have been registered +// for this indicator number by an earlier call to the RegisterDecoder function. +func (r *Registry) LookupDecoder(indicator uint64) (ipld.Decoder, error) { + decodeFunc, exists := r.decoders[indicator] + if !exists { + return nil, fmt.Errorf("no decoder registered for multicodec code %d (0x%x)", indicator, indicator) + } + return decodeFunc, nil +}