func (s *remoteStore) GetTemplate(ids *ttnpb.EndDeviceVersionIdentifiers) (*ttnpb.EndDeviceTemplate, error) {
	models, err := s.GetModels(store.GetModelsRequest{
		BrandID: ids.BrandId,
		ModelID: ids.ModelId,
		Paths: []string{
			"firmware_versions",
		},
	})
	if err != nil {
		return nil, err
	}
	if len(models.Models) == 0 {
		return nil, errModelNotFound.WithAttributes("brand_id", ids.BrandId, "model_id", ids.ModelId)
	}
	model := models.Models[0]
	for _, ver := range model.FirmwareVersions {
		if ver.Version != ids.FirmwareVersion {
			continue
		}
		if _, ok := bandIDToRegion[ids.BandId]; !ok {
			return nil, errBandNotFound.WithAttributes("unknown_band", ids.BandId)
		}
		profileInfo, ok := ver.Profiles[ids.BandId]
		if !ok {
			return nil, errNoProfileForBand.WithAttributes(
				"band_id", ids.BandId,
			)
		}
		profileVendorID := ids.BrandId
		if id := profileInfo.VendorId; id != "" {
			profileVendorID = id
		}
		b, err := s.fetcher.File("vendor", profileVendorID, profileInfo.ProfileId+".yaml")
		if err != nil {
			return nil, err
		}
		profile := EndDeviceProfile{}
		if err := yaml.Unmarshal(b, &profile); err != nil {
			return nil, err
		}
		return profile.ToTemplatePB(ids, profileInfo)
	}
	return nil, errFirmwareVersionNotFound.WithAttributes(
		"brand_id", ids.BrandId,
		"model_id", ids.ModelId,
		"firmware_version", ids.FirmwareVersion,
	)
}
GetTemplate retrieves an end device template for an end device definition.
package remote
import (
	"go.thethings.network/lorawan-stack/v3/pkg/devicerepository/store"
	"go.thethings.network/lorawan-stack/v3/pkg/errors"
	"go.thethings.network/lorawan-stack/v3/pkg/fetch"
	"go.thethings.network/lorawan-stack/v3/pkg/gogoproto"
	"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
	"gopkg.in/yaml.v2"
)
type remoteStore struct {
	fetcher fetch.Interface
}
func NewRemoteStore(fetcher fetch.Interface) store.Store {
	return &remoteStore{fetcher}
}
func paginate(size int, limit, page uint32) (uint32, uint32) {
	if page == 0 {
		page = 1
	}
	offset := (page - 1) * limit
	start, end := offset, uint32(size)
	if start >= end {
		return 0, 0
	}
	if limit > 0 && start+limit < end {
		end = start + limit
	}
	return start, end
}
func (s *remoteStore) GetBrands(req store.GetBrandsRequest) (*store.GetBrandsResponse, error) {
	b, err := s.fetcher.File("vendor", "index.yaml")
	if err != nil {
		return nil, err
	}
	rawVendors := VendorsIndex{}
	if err := yaml.Unmarshal(b, &rawVendors); err != nil {
		return nil, err
	}
	brands := make([]*ttnpb.EndDeviceBrand, 0, len(rawVendors.Vendors))
	for _, vendor := range rawVendors.Vendors {
		if vendor.Draft {
			continue
		}
		pb, err := vendor.ToPB(req.Paths...)
		if err != nil {
			return nil, err
		}
		brands = append(brands, pb)
	}
	start, end := paginate(len(brands), req.Limit, req.Page)
	return &store.GetBrandsResponse{
		Count:  end - start,
		Offset: start,
		Total:  uint32(len(brands)),
		Brands: brands[start:end],
	}, nil
}
var errBrandNotFound = errors.DefineNotFound("brand_not_found", "brand `{brand_id}` not found")
func (s *remoteStore) listModelsByBrand(req store.GetModelsRequest) (*store.GetModelsResponse, error) {
	b, err := s.fetcher.File("vendor", req.BrandID, "index.yaml")
	if err != nil {
		return nil, errBrandNotFound.WithAttributes("brand_id", req.BrandID)
	}
	index := VendorEndDevicesIndex{}
	if err := yaml.Unmarshal(b, &index); err != nil {
		return nil, err
	}
	start, end := paginate(len(index.EndDevices), req.Limit, req.Page)
	models := make([]*ttnpb.EndDeviceModel, 0, end-start)
	for idx := start; idx < end; idx++ {
		modelID := index.EndDevices[idx]
		if req.ModelID != "" && modelID != req.ModelID {
			continue
		}
		b, err := s.fetcher.File("vendor", req.BrandID, modelID+".yaml")
		if err != nil {
			return nil, err
		}
		model := EndDeviceModel{}
		if err := yaml.Unmarshal(b, &model); err != nil {
			return nil, err
		}
		pb, err := model.ToPB(req.BrandID, modelID, req.Paths...)
		if err != nil {
			return nil, err
		}
		models = append(models, pb)
	}
	return &store.GetModelsResponse{
		Count:  end - start,
		Offset: start,
		Total:  uint32(len(index.EndDevices)),
		Models: models,
	}, nil
}
func (s *remoteStore) GetModels(req store.GetModelsRequest) (*store.GetModelsResponse, error) {
	if req.BrandID != "" {
		return s.listModelsByBrand(req)
	}
	all := []*ttnpb.EndDeviceModel{}
	brands, err := s.GetBrands(store.GetBrandsRequest{
		Paths: []string{"brand_id"},
	})
	if err != nil {
		return nil, err
	}
	for _, brand := range brands.Brands {
		models, err := s.GetModels(store.GetModelsRequest{
			Paths:   req.Paths,
			BrandID: brand.BrandId,
			Limit:   req.Limit,
		})
		if errors.IsNotFound(err) {
			continue
		} else if err != nil {
			return nil, err
		}
		all = append(all, models.Models...)
	}
	start, end := paginate(len(all), req.Limit, req.Page)
	return &store.GetModelsResponse{
		Count:  end - start,
		Offset: start,
		Total:  uint32(len(all)),
		Models: all[start:end],
	}, nil
}
var (
	errModelNotFound           = errors.DefineNotFound("model_not_found", "model `{brand_id}/{model_id}` not found")
	errBandNotFound            = errors.DefineNotFound("band_not_found", "band `{band_id}` not found")
	errNoProfileForBand        = errors.DefineNotFound("no_profile_for_band", "device does not have a profile for band `{band_id}`")
	errFirmwareVersionNotFound = errors.DefineNotFound("firmware_version_not_found", "firmware version `{firmware_version}` for model `{brand_id}/{model_id}` not found")
)