Repository: dragonflyoss/Dragonfly2
Pull request #1292 (2022-05-07 06:27:09)

feat: support response header
Signed-off-by: Jim Ma <majinjing3@gmail.com>

<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.


codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#1292](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (dada5b6) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/78600de04221fa6e5ac1b301d235ccdbd8ec212c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (78600de) will **decrease** coverage by `0.28%`.
> The diff coverage is `38.35%`.

[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/graphs/tree.svg?width=650&height=150&src=pr&token=PUICJYSAT7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@            Coverage Diff             @@
##             main    #1292      +/-   ##
==========================================
- Coverage   46.33%   46.05%   -0.29%     
==========================================
  Files         137      138       +1     
  Lines       11625    11748     +123     
==========================================
+ Hits         5386     5410      +24     
- Misses       5649     5729      +80     
- Partials      590      609      +19     
```

| Flag | Coverage Δ | |
|---|---|---|
| Object-compatibility-e2etests | `?` | |
| e2etests | `?` | |
| unittests | `46.05% <38.35%> (-0.29%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [client/daemon/peer/peertask\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX21hbmFnZXIuZ28=) | `23.39% <0.00%> (-0.28%)` | :arrow_down: |
| [client/daemon/storage/local\_storage.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9zdG9yYWdlL2xvY2FsX3N0b3JhZ2UuZ28=) | `28.11% <0.00%> (-0.85%)` | :arrow_down: |
| [client/daemon/storage/local\_storage\_subtask.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9zdG9yYWdlL2xvY2FsX3N0b3JhZ2Vfc3VidGFzay5nbw==) | `27.52% <0.00%> (-1.05%)` | :arrow_down: |
| [client/daemon/storage/storage\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9zdG9yYWdlL3N0b3JhZ2VfbWFuYWdlci5nbw==) | `16.82% <0.00%> (-0.27%)` | :arrow_down: |
| [client/daemon/transport/transport.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi90cmFuc3BvcnQvdHJhbnNwb3J0Lmdv) | `51.38% <0.00%> (ø)` | |
| [pkg/source/clients/httpprotocol/header.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL3NvdXJjZS9jbGllbnRzL2h0dHBwcm90b2NvbC9oZWFkZXIuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/source/response.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL3NvdXJjZS9yZXNwb25zZS5nbw==) | `0.00% <0.00%> (ø)` | |
| [client/daemon/peer/peertask\_stream.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX3N0cmVhbS5nbw==) | `59.09% <16.66%> (-3.59%)` | :arrow_down: |
| [client/daemon/peer/peertask\_conductor.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX2NvbmR1Y3Rvci5nbw==) | `57.12% <21.05%> (-1.96%)` | :arrow_down: |
| [cdn/supervisor/cdn/downloader.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2RuL3N1cGVydmlzb3IvY2RuL2Rvd25sb2FkZXIuZ28=) | `47.05% <33.33%> (-1.33%)` | :arrow_down: |
| ... and [9 more](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1292/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | |


Patch:
diff --git a/cdn/rpcserver/rpcserver.go b/cdn/rpcserver/rpcserver.go
index 3af51c55..2df63b4b 100644
--- a/cdn/rpcserver/rpcserver.go
+++ b/cdn/rpcserver/rpcserver.go
@@ -103,6 +103,9 @@ func (css *Server) ObtainSeeds(ctx context.Context, req *cdnsystem.SeedRequest,
 		span.RecordError(err)
 		return err
 	}
+	var (
+		extendAttributeSend bool
+	)
 	for piece := range pieceChan {
 		pieceSeed := &cdnsystem.PieceSeed{
 			PeerId: peerID,
@@ -122,6 +125,11 @@ func (css *Server) ObtainSeeds(ctx context.Context, req *cdnsystem.SeedRequest,
 			BeginTime:       piece.BeginDownloadTime,
 			EndTime:         piece.EndDownloadTime,
 		}
+		// only send extend attribute once
+		if !extendAttributeSend {
+			extendAttributeSend = true
+			pieceSeed.ExtendAttribute = registeredTask.ExtendAttribute
+		}
 		psc <- pieceSeed
 		jsonPiece, err := json.Marshal(pieceSeed)
 		if err != nil {
@@ -232,13 +240,14 @@ func (css *Server) GetPieceTasks(ctx context.Context, req *base.PieceTaskRequest
 		pieceMd5Sign = digestutils.Sha256(pieceMd5s...)
 	}
 	pp := &base.PiecePacket{
-		TaskId:        req.TaskId,
-		DstPid:        req.DstPid,
-		DstAddr:       fmt.Sprintf("%s:%d", css.config.AdvertiseIP, css.config.DownloadPort),
-		PieceInfos:    pieceInfos,
-		TotalPiece:    seedTask.TotalPieceCount,
-		ContentLength: seedTask.SourceFileLength,
-		PieceMd5Sign:  pieceMd5Sign,
+		TaskId:          req.TaskId,
+		DstPid:          req.DstPid,
+		DstAddr:         fmt.Sprintf("%s:%d", css.config.AdvertiseIP, css.config.DownloadPort),
+		PieceInfos:      pieceInfos,
+		TotalPiece:      seedTask.TotalPieceCount,
+		ContentLength:   seedTask.SourceFileLength,
+		PieceMd5Sign:    pieceMd5Sign,
+		ExtendAttribute: seedTask.ExtendAttribute,
 	}
 	span.SetAttributes(constants.AttributePiecePacketResult.String(pp.String()))
 	return pp, nil
diff --git a/cdn/supervisor/cdn/downloader.go b/cdn/supervisor/cdn/downloader.go
index 45b44fac..7ed08909 100644
--- a/cdn/supervisor/cdn/downloader.go
+++ b/cdn/supervisor/cdn/downloader.go
@@ -24,39 +24,56 @@ import (
 	"github.com/pkg/errors"
 
 	"d7y.io/dragonfly/v2/cdn/supervisor/task"
+	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
 )
 
-func (cm *manager) download(ctx context.Context, seedTask *task.SeedTask, breakPoint int64) (io.ReadCloser, error) {
+func (cm *manager) download(ctx context.Context, seedTask *task.SeedTask, breakPoint int64) (io.ReadCloser, *base.ExtendAttribute, error) {
 	var err error
 	breakRange := seedTask.Range
 	if breakPoint > 0 {
 		// todo replace task.SourceFileLength with totalSourceFileLength to get BreakRange
 		breakRange, err = getBreakRange(breakPoint, seedTask.Range, seedTask.SourceFileLength)
 		if err != nil {
-			return nil, errors.Wrapf(err, "calculate the breakRange")
+			return nil, nil, errors.Wrapf(err, "calculate the breakRange")
 		}
 	}
 	seedTask.Log().Infof("start downloading %s at range %s with header %s", seedTask.RawURL, breakRange, seedTask.Header)
 	downloadRequest, err := source.NewRequestWithContext(ctx, seedTask.RawURL, seedTask.Header)
 	if err != nil {
-		return nil, errors.Wrap(err, "create download request")
+		return nil, nil, errors.Wrap(err, "create download request")
 	}
 	if !stringutils.IsBlank(breakRange) {
 		downloadRequest.Header.Add(source.Range, breakRange)
 	}
 	response, err := source.Download(downloadRequest)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
+	}
+	err = response.Validate()
+	if err != nil {
+		return nil, nil, err
+	}
+	var (
+		exa *base.ExtendAttribute
+	)
+	if len(response.Header) > 0 {
+		var header = map[string]string{}
+		for k, v := range response.Header {
+			if len(v) > 0 {
+				header[k] = response.Header.Get(k)
+			}
+		}
+		exa = &base.ExtendAttribute{Header: header}
 	}
 	// update Expire info
-	cm.updateExpireInfo(seedTask.ID, map[string]string{
+	cm.updateResponseMetadata(seedTask.ID, map[string]string{
 		source.LastModified: response.Header.Get(source.LastModified),
 		source.ETag:         response.Header.Get(source.ETag),
-	})
-	return response.Body, err
+	}, exa)
+	return response.Body, exa, err
 }
 
 func getBreakRange(breakPoint int64, taskRange string, fileTotalLength int64) (string, error) {
diff --git a/cdn/supervisor/cdn/manager.go b/cdn/supervisor/cdn/manager.go
index 8980305d..ef81cdde 100644
--- a/cdn/supervisor/cdn/manager.go
+++ b/cdn/supervisor/cdn/manager.go
@@ -36,6 +36,7 @@ import (
 	logger "d7y.io/dragonfly/v2/internal/dflog"
 	"d7y.io/dragonfly/v2/pkg/ratelimiter/limitreader"
 	"d7y.io/dragonfly/v2/pkg/ratelimiter/ratelimiter"
+	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/cdnsystem/server"
 	"d7y.io/dragonfly/v2/pkg/synclock"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
@@ -151,7 +152,7 @@ func (cm *manager) doTrigger(ctx context.Context, seedTask *task.SeedTask) (*tas
 	if detectResult.BreakPoint == -1 {
 		seedTask.Log().Infof("cache full hit on local")
 		return getUpdateTaskInfo(seedTask, task.StatusSuccess, detectResult.FileMetadata.SourceRealDigest, detectResult.FileMetadata.PieceMd5Sign,
-			detectResult.FileMetadata.SourceFileLen, detectResult.FileMetadata.CdnFileLength, detectResult.FileMetadata.TotalPieceCount), nil
+			detectResult.FileMetadata.SourceFileLen, detectResult.FileMetadata.CdnFileLength, detectResult.FileMetadata.TotalPieceCount, detectResult.FileMetadata.ExtendAttribute), nil
 	}
 
 	start := time.Now()
@@ -160,13 +161,15 @@ func (cm *manager) doTrigger(ctx context.Context, seedTask *task.SeedTask) (*tas
 	ctx, downloadSpan = tracer.Start(ctx, constants.SpanDownloadSource)
 	downloadSpan.End()
 	server.StatSeedStart(seedTask.ID, seedTask.RawURL)
-	respBody, err := cm.download(ctx, seedTask, detectResult.BreakPoint)
+	respBody, extendAttribute, err := cm.download(ctx, seedTask, detectResult.BreakPoint)
 	// download fail
 	if err != nil {
 		downloadSpan.RecordError(err)
 		server.StatSeedFinish(seedTask.ID, seedTask.RawURL, false, err, start, time.Now(), 0, 0)
 		return nil, errors.Wrap(err, "download task file data")
 	}
+	seedTask.ExtendAttribute = extendAttribute
+
 	defer respBody.Close()
 	reader := limitreader.NewLimitReaderWithLimiterAndDigest(respBody, cm.limiter, fileDigest, digestutils.Algorithms[digestType])
 
@@ -185,7 +188,7 @@ func (cm *manager) doTrigger(ctx context.Context, seedTask *task.SeedTask) (*tas
 		return nil, err
 	}
 	return getUpdateTaskInfo(seedTask, task.StatusSuccess, downloadMetadata.sourceRealDigest, downloadMetadata.pieceMd5Sign,
-		downloadMetadata.realSourceFileLength, downloadMetadata.realCdnFileLength, downloadMetadata.totalPieceCount), nil
+		downloadMetadata.realSourceFileLength, downloadMetadata.realCdnFileLength, downloadMetadata.totalPieceCount, extendAttribute), nil
 }
 
 func (cm *manager) Delete(taskID string) error {
@@ -242,8 +245,8 @@ func (cm *manager) handleCDNResult(seedTask *task.SeedTask, downloadMetadata *do
 	return nil
 }
 
-func (cm *manager) updateExpireInfo(taskID string, expireInfo map[string]string) {
-	if err := cm.metadataManager.updateExpireInfo(taskID, expireInfo); err != nil {
+func (cm *manager) updateResponseMetadata(taskID string, expireInfo map[string]string, exa *base.ExtendAttribute) {
+	if err := cm.metadataManager.updateResponseMetadata(taskID, expireInfo, exa); err != nil {
 		logger.WithTaskID(taskID).Errorf("failed to update expireInfo(%s): %v", expireInfo, err)
 	}
 	logger.WithTaskID(taskID).Debugf("success to update metadata expireInfo(%s)", expireInfo)
@@ -261,7 +264,7 @@ func getUpdateTaskInfoWithStatusOnly(seedTask *task.SeedTask, cdnStatus string)
 }
 
 func getUpdateTaskInfo(seedTask *task.SeedTask, cdnStatus, realMD5, pieceMd5Sign string, sourceFileLength, cdnFileLength int64,
-	totalPieceCount int32) *task.SeedTask {
+	totalPieceCount int32, exa *base.ExtendAttribute) *task.SeedTask {
 	cloneTask := seedTask.Clone()
 	cloneTask.SourceFileLength = sourceFileLength
 	cloneTask.CdnFileLength = cdnFileLength
@@ -269,5 +272,6 @@ func getUpdateTaskInfo(seedTask *task.SeedTask, cdnStatus, realMD5, pieceMd5Sign
 	cloneTask.TotalPieceCount = totalPieceCount
 	cloneTask.SourceRealDigest = realMD5
 	cloneTask.PieceMd5Sign = pieceMd5Sign
+	cloneTask.ExtendAttribute = exa
 	return cloneTask
 }
diff --git a/cdn/supervisor/cdn/metadata_manager.go b/cdn/supervisor/cdn/metadata_manager.go
index f3555e33..c29e9571 100644
--- a/cdn/supervisor/cdn/metadata_manager.go
+++ b/cdn/supervisor/cdn/metadata_manager.go
@@ -24,6 +24,7 @@ import (
 	"d7y.io/dragonfly/v2/cdn/supervisor/cdn/storage"
 	"d7y.io/dragonfly/v2/cdn/supervisor/task"
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/synclock"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 	"d7y.io/dragonfly/v2/pkg/util/stringutils"
@@ -90,7 +91,7 @@ func (mm *metadataManager) updateAccessTime(taskID string, accessTime int64) err
 	return mm.storage.WriteFileMetadata(taskID, originMetadata)
 }
 
-func (mm *metadataManager) updateExpireInfo(taskID string, expireInfo map[string]string) error {
+func (mm *metadataManager) updateResponseMetadata(taskID string, expireInfo map[string]string, exa *base.ExtendAttribute) error {
 	mm.cacheLocker.Lock(taskID, false)
 	defer mm.cacheLocker.UnLock(taskID, false)
 
@@ -100,6 +101,7 @@ func (mm *metadataManager) updateExpireInfo(taskID string, expireInfo map[string
 	}
 
 	originMetadata.ExpireInfo = expireInfo
+	originMetadata.ExtendAttribute = exa
 
 	return mm.storage.WriteFileMetadata(taskID, originMetadata)
 }
diff --git a/cdn/supervisor/cdn/storage/storage.go b/cdn/supervisor/cdn/storage/storage.go
index 6a30efe1..cada2b36 100644
--- a/cdn/supervisor/cdn/storage/storage.go
+++ b/cdn/supervisor/cdn/storage/storage.go
@@ -28,6 +28,7 @@ import (
 
 	"d7y.io/dragonfly/v2/cdn/storedriver"
 	"d7y.io/dragonfly/v2/cdn/supervisor/task"
+	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/util/rangeutils"
 )
 
@@ -81,23 +82,24 @@ type Manager interface {
 
 // FileMetadata meta data of task
 type FileMetadata struct {
-	TaskID           string            `json:"taskID"`
-	TaskURL          string            `json:"taskURL"`
-	PieceSize        int32             `json:"pieceSize"`
-	SourceFileLen    int64             `json:"sourceFileLen"`
-	AccessTime       int64             `json:"accessTime"`
-	Interval         int64             `json:"interval"`
-	CdnFileLength    int64             `json:"cdnFileLength"`
-	Digest           string            `json:"digest"`
-	SourceRealDigest string            `json:"sourceRealDigest"`
-	Tag              string            `json:"tag"`
-	ExpireInfo       map[string]string `json:"expireInfo"`
-	Finish           bool              `json:"finish"`
-	Success          bool              `json:"success"`
-	TotalPieceCount  int32             `json:"totalPieceCount"`
-	PieceMd5Sign     string            `json:"pieceMd5Sign"`
-	Range            string            `json:"range"`
-	Filter           string            `json:"filter"`
+	TaskID           string                `json:"taskID"`
+	TaskURL          string                `json:"taskURL"`
+	PieceSize        int32                 `json:"pieceSize"`
+	SourceFileLen    int64                 `json:"sourceFileLen"`
+	AccessTime       int64                 `json:"accessTime"`
+	Interval         int64                 `json:"interval"`
+	CdnFileLength    int64                 `json:"cdnFileLength"`
+	Digest           string                `json:"digest"`
+	SourceRealDigest string                `json:"sourceRealDigest"`
+	Tag              string                `json:"tag"`
+	ExpireInfo       map[string]string     `json:"expireInfo"`
+	Finish           bool                  `json:"finish"`
+	Success          bool                  `json:"success"`
+	TotalPieceCount  int32                 `json:"totalPieceCount"`
+	PieceMd5Sign     string                `json:"pieceMd5Sign"`
+	Range            string                `json:"range"`
+	Filter           string                `json:"filter"`
+	ExtendAttribute  *base.ExtendAttribute `json:"extendAttribute"`
 }
 
 // PieceMetaRecord meta data of piece
diff --git a/cdn/supervisor/task/task.go b/cdn/supervisor/task/task.go
index fd06be59..3796841a 100644
--- a/cdn/supervisor/task/task.go
+++ b/cdn/supervisor/task/task.go
@@ -86,6 +86,8 @@ type SeedTask struct {
 	// Pieces pieces of task
 	Pieces *sync.Map `json:"-"` // map[uint32]*PieceInfo
 
+	ExtendAttribute *base.ExtendAttribute `json:"extendAttribute"`
+
 	logger *logger.SugaredLoggerOnWith
 }
 
@@ -216,5 +218,5 @@ const (
 )
 
 func IsEqual(task1, task2 SeedTask) bool {
-	return cmp.Equal(task1, task2, cmpopts.IgnoreFields(SeedTask{}, "Pieces"), cmpopts.IgnoreUnexported(SeedTask{}))
+	return cmp.Equal(task1, task2, cmpopts.IgnoreFields(SeedTask{}, "Pieces"), cmpopts.IgnoreUnexported(SeedTask{}, base.ExtendAttribute{}))
 }
diff --git a/client/daemon/peer/peertask_conductor.go b/client/daemon/peer/peertask_conductor.go
index ea6a4405..b59661bf 100644
--- a/client/daemon/peer/peertask_conductor.go
+++ b/client/daemon/peer/peertask_conductor.go
@@ -41,6 +41,7 @@ import (
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
 	"d7y.io/dragonfly/v2/pkg/rpc/scheduler"
 	schedulerclient "d7y.io/dragonfly/v2/pkg/rpc/scheduler/client"
+	"d7y.io/dragonfly/v2/pkg/source"
 	"d7y.io/dragonfly/v2/pkg/util/digestutils"
 )
 
@@ -98,6 +99,7 @@ type peerTaskConductor struct {
 	contentLength   *atomic.Int64
 	completedLength *atomic.Int64
 	usedTraffic     *atomic.Uint64
+	header          atomic.Value
 
 	broker *pieceBroker
 
@@ -287,6 +289,7 @@ func (pt *peerTaskConductor) register() error {
 		pt.Infof("register task success, SizeScope: %s", base.SizeScope_name[int32(result.SizeScope)])
 	}
 
+	var header map[string]string
 	if !needBackSource {
 		sizeScope = result.SizeScope
 		switch result.SizeScope {
@@ -297,6 +300,9 @@ func (pt *peerTaskConductor) register() error {
 			if piece, ok := result.DirectPiece.(*scheduler.RegisterResult_SinglePiece); ok {
 				singlePiece = piece.SinglePiece
 			}
+			if result.ExtendAttribute != nil {
+				header = result.ExtendAttribute.Header
+			}
 		case base.SizeScope_TINY:
 			pt.span.SetAttributes(config.AttributePeerTaskSizeScope.String("tiny"))
 			if piece, ok := result.DirectPiece.(*scheduler.RegisterResult_PieceContent); ok {
@@ -312,6 +318,9 @@ func (pt *peerTaskConductor) register() error {
 				pt.cancel(base.Code_SchedError, err.Error())
 				return err
 			}
+			if result.ExtendAttribute != nil {
+				header = result.ExtendAttribute.Header
+			}
 		}
 	}
 
@@ -328,6 +337,10 @@ func (pt *peerTaskConductor) register() error {
 	pt.singlePiece = singlePiece
 	pt.tinyData = tinyData
 	pt.needBackSource = atomic.NewBool(needBackSource)
+
+	if len(header) > 0 {
+		pt.SetHeader(header)
+	}
 	return nil
 }
 
@@ -394,6 +407,22 @@ func (pt *peerTaskConductor) GetPieceMd5Sign() string {
 	return pt.digest.Load()
 }
 
+func (pt *peerTaskConductor) SetHeader(header map[string]string) {
+	var hdr = &source.Header{}
+	for k, v := range header {
+		hdr.Set(k, v)
+	}
+	pt.header.Store(hdr)
+}
+
+func (pt *peerTaskConductor) GetHeader() *source.Header {
+	hdr := pt.header.Load()
+	if hdr != nil {
+		return hdr.(*source.Header)
+	}
+	return nil
+}
+
 func (pt *peerTaskConductor) Context() context.Context {
 	return pt.ctx
 }
@@ -916,6 +945,12 @@ func (pt *peerTaskConductor) updateMetadata(piecePacket *base.PiecePacket) {
 		pt.Debugf("update content length: %d", piecePacket.ContentLength)
 	}
 
+	if piecePacket.ExtendAttribute != nil && len(piecePacket.ExtendAttribute.Header) > 0 && pt.GetHeader() == nil {
+		metadataChanged = true
+		pt.SetHeader(piecePacket.ExtendAttribute.Header)
+		pt.Debugf("update response header: %#v", piecePacket.ExtendAttribute.Header)
+	}
+
 	if metadataChanged {
 		err := pt.UpdateStorage()
 		if err != nil {
@@ -1362,6 +1397,7 @@ func (pt *peerTaskConductor) UpdateStorage() error {
 			ContentLength: pt.GetContentLength(),
 			TotalPieces:   pt.GetTotalPieces(),
 			PieceMd5Sign:  pt.GetPieceMd5Sign(),
+			Header:        pt.GetHeader(),
 		})
 	if err != nil {
 		pt.Log().Errorf("update task to storage manager failed: %s", err)
diff --git a/client/daemon/peer/peertask_manager.go b/client/daemon/peer/peertask_manager.go
index d461d4eb..b850dbac 100644
--- a/client/daemon/peer/peertask_manager.go
+++ b/client/daemon/peer/peertask_manager.go
@@ -55,7 +55,7 @@ type TaskManager interface {
 
 	Subscribe(request *base.PieceTaskRequest) (*SubscribeResponse, bool)
 
-	IsPeerTaskRunning(id string) bool
+	IsPeerTaskRunning(taskID string) (Task, bool)
 
 	// StatTask checks whether the given task exists in P2P network
 	StatTask(ctx context.Context, taskID string) (*scheduler.Task, error)
@@ -381,9 +381,12 @@ func (ptm *peerTaskManager) PeerTaskDone(taskID string) {
 	ptm.runningPeerTasks.Delete(taskID)
 }
 
-func (ptm *peerTaskManager) IsPeerTaskRunning(taskID string) bool {
-	_, ok := ptm.runningPeerTasks.Load(taskID)
-	return ok
+func (ptm *peerTaskManager) IsPeerTaskRunning(taskID string) (Task, bool) {
+	ptc, ok := ptm.runningPeerTasks.Load(taskID)
+	if ok {
+		return ptc.(*peerTaskConductor), ok
+	}
+	return nil, ok
 }
 
 func (ptm *peerTaskManager) StatTask(ctx context.Context, taskID string) (*scheduler.Task, error) {
diff --git a/client/daemon/peer/peertask_reuse.go b/client/daemon/peer/peertask_reuse.go
index c347fec6..a9607585 100644
--- a/client/daemon/peer/peertask_reuse.go
+++ b/client/daemon/peer/peertask_reuse.go
@@ -240,11 +240,25 @@ func (ptm *peerTaskManager) tryReuseStreamPeerTask(ctx context.Context,
 		return nil, nil, false
 	}
 
+	exa, err := ptm.storageManager.GetExtendAttribute(ctx, &reuse.PeerTaskMetadata)
+	if err != nil {
+		log.Errorf("get extend attribute error when reuse peer task: %s", err)
+		span.SetAttributes(config.AttributePeerTaskSuccess.Bool(false))
+		span.RecordError(err)
+		return nil, nil, false
+	}
+
 	attr := map[string]string{}
 	attr[config.HeaderDragonflyTask] = taskID
 	attr[config.HeaderDragonflyPeer] = request.PeerID
 	attr[headers.ContentLength] = fmt.Sprintf("%d", length)
 
+	if exa != nil {
+		for k, v := range exa.Header {
+			attr[k] = v
+		}
+	}
+
 	if reuseRange != nil {
 		attr[config.HeaderDragonflyRange] = request.URLMeta.Range
 		attr[headers.ContentRange] = fmt.Sprintf("bytes %d-%d/%d", reuseRange.Start,
diff --git a/client/daemon/peer/peertask_stream.go b/client/daemon/peer/peertask_stream.go
index 729dded2..fbe27604 100644
--- a/client/daemon/peer/peertask_stream.go
+++ b/client/daemon/peer/peertask_stream.go
@@ -121,6 +121,16 @@ func (s *streamTask) Start(ctx context.Context) (io.ReadCloser, map[string]strin
 		} else {
 			attr[headers.TransferEncoding] = "chunked"
 		}
+		exa, err := s.peerTaskConductor.storage.GetExtendAttribute(ctx, nil)
+		if err != nil {
+			s.Errorf("read extend attribute error due to %s ", err.Error())
+			return nil, attr, err
+		}
+		if exa != nil {
+			for k, v := range exa.Header {
+				attr[k] = v
+			}
+		}
 		rc, err := s.peerTaskConductor.peerTaskManager.storageManager.ReadAllPieces(
 			ctx,
 			&storage.ReadAllPiecesRequest{
@@ -131,6 +141,16 @@ func (s *streamTask) Start(ctx context.Context) (io.ReadCloser, map[string]strin
 		return rc, attr, err
 	case first := <-s.pieceCh:
 		firstPiece = first
+		exa, err := s.peerTaskConductor.storage.GetExtendAttribute(ctx, nil)
+		if err != nil {
+			s.Errorf("read extend attribute error due to %s ", err.Error())
+			return nil, attr, err
+		}
+		if exa != nil {
+			for k, v := range exa.Header {
+				attr[k] = v
+			}
+		}
 	}
 
 	if s.peerTaskConductor.GetContentLength() != -1 {
diff --git a/client/daemon/peer/piece_manager.go b/client/daemon/peer/piece_manager.go
index 7a958aa4..8c1efab3 100644
--- a/client/daemon/peer/piece_manager.go
+++ b/client/daemon/peer/piece_manager.go
@@ -268,6 +268,10 @@ func (pm *pieceManager) DownloadSource(ctx context.Context, pt Task, request *sc
 	if err != nil {
 		return err
 	}
+	err = response.Validate()
+	if err != nil {
+		return err
+	}
 	contentLength := response.ContentLength
 	if contentLength < 0 {
 		log.Warnf("can not get content length for %s", request.Url)
@@ -280,6 +284,7 @@ func (pm *pieceManager) DownloadSource(ctx context.Context, pt Task, request *sc
 					TaskID: pt.GetTaskID(),
 				},
 				ContentLength: contentLength,
+				Header:        &response.Header,
 			})
 		if err != nil {
 			return err
@@ -362,6 +367,7 @@ func (pm *pieceManager) downloadKnownLengthSource(ctx context.Context, pt Task,
 					ContentLength: contentLength,
 					TotalPieces:   maxPieceNum,
 				})
+			pt.SetTotalPieces(maxPieceNum)
 			if err != nil {
 				log.Errorf("update task failed %s", err)
 				pt.ReportPieceResult(request, result, detectBackSourceError(err))
diff --git a/client/daemon/rpcserver/rpcserver.go b/client/daemon/rpcserver/rpcserver.go
index 77d60ceb..81660c84 100644
--- a/client/daemon/rpcserver/rpcserver.go
+++ b/client/daemon/rpcserver/rpcserver.go
@@ -26,6 +26,7 @@ import (
 	"time"
 
 	"github.com/pkg/errors"
+	"go.uber.org/atomic"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/health"
@@ -121,7 +122,7 @@ func (s *server) GetPieceTasks(ctx context.Context, request *base.PieceTaskReque
 			return nil, dferrors.New(code, err.Error())
 		}
 		// dst peer is not running
-		if !s.peerTaskManager.IsPeerTaskRunning(request.TaskId) {
+		if task, ok := s.peerTaskManager.IsPeerTaskRunning(request.TaskId); !ok || task.GetPeerID() != request.GetDstPid() {
 			code = base.Code_PeerTaskNotFound
 			logger.Errorf("get piece tasks error: peer task not found, task id: %s, src peer: %s, dst peer: %s, piece num: %d, limit: %d",
 				request.TaskId, request.SrcPid, request.DstPid, request.StartNum, request.Limit)
@@ -152,13 +153,23 @@ func (s *server) GetPieceTasks(ctx context.Context, request *base.PieceTaskReque
 }
 
 // sendExistPieces will send as much as possible pieces
-func (s *server) sendExistPieces(log *logger.SugaredLoggerOnWith, request *base.PieceTaskRequest, sync dfdaemongrpc.Daemon_SyncPieceTasksServer, sentMap map[int32]struct{}) (total int32, err error) {
-	return sendExistPieces(sync.Context(), log, s.GetPieceTasks, request, sync, sentMap, true)
+func (s *server) sendExistPieces(
+	log *logger.SugaredLoggerOnWith,
+	request *base.PieceTaskRequest,
+	sync dfdaemongrpc.Daemon_SyncPieceTasksServer,
+	get func(ctx context.Context, request *base.PieceTaskRequest) (*base.PiecePacket, error),
+	sentMap map[int32]struct{}) (total int32, err error) {
+	return sendExistPieces(sync.Context(), log, get, request, sync, sentMap, true)
 }
 
 // sendFirstPieceTasks will send as much as possible pieces, even if no available pieces
-func (s *server) sendFirstPieceTasks(log *logger.SugaredLoggerOnWith, request *base.PieceTaskRequest, sync dfdaemongrpc.Daemon_SyncPieceTasksServer, sentMap map[int32]struct{}) (total int32, err error) {
-	return sendExistPieces(sync.Context(), log, s.GetPieceTasks, request, sync, sentMap, false)
+func (s *server) sendFirstPieceTasks(
+	log *logger.SugaredLoggerOnWith,
+	request *base.PieceTaskRequest,
+	sync dfdaemongrpc.Daemon_SyncPieceTasksServer,
+	get func(ctx context.Context, request *base.PieceTaskRequest) (*base.PiecePacket, error),
+	sentMap map[int32]struct{}) (total int32, err error) {
+	return sendExistPieces(sync.Context(), log, get, request, sync, sentMap, false)
 }
 
 func (s *server) SyncPieceTasks(sync dfdaemongrpc.Daemon_SyncPieceTasksServer) error {
@@ -171,10 +182,35 @@ func (s *server) SyncPieceTasks(sync dfdaemongrpc.Daemon_SyncPieceTasksServer) e
 		"localPeerID", request.DstPid, "remotePeerID", request.SrcPid)
 
 	skipPieceCount := request.StartNum
-	var sentMap = make(map[int32]struct{})
+	var (
+		sentMap       = make(map[int32]struct{})
+		attributeSent bool
+	)
+
+	getPieces := func(ctx context.Context, request *base.PieceTaskRequest) (*base.PiecePacket, error) {
+		p, e := s.GetPieceTasks(ctx, request)
+		if e != nil {
+			return nil, e
+		}
+		p.DstAddr = s.uploadAddr
+		if !attributeSent && len(p.PieceInfos) > 0 {
+			exa, e := s.storageManager.GetExtendAttribute(ctx,
+				&storage.PeerTaskMetadata{
+					PeerID: request.TaskId,
+					TaskID: request.DstPid,
+				})
+			if e != nil {
+				log.Errorf("get extend attribute error: %s", e.Error())
+				return nil, e
+			}
+			p.ExtendAttribute = exa
+			attributeSent = true
+		}
+		return p, e
+	}
 
 	// TODO if not found, try to send to peer task conductor, then download it first
-	total, err := s.sendFirstPieceTasks(log, request, sync, sentMap)
+	total, err := s.sendFirstPieceTasks(log, request, sync, getPieces, sentMap)
 	if err != nil {
 		log.Errorf("send first piece tasks error: %s", err)
 		return err
@@ -190,7 +226,7 @@ func (s *server) SyncPieceTasks(sync dfdaemongrpc.Daemon_SyncPieceTasksServer) e
 				logger.Errorf("receive reminding piece tasks request error: %s", err)
 				return err
 			}
-			total, err = s.sendExistPieces(log, request, sync, sentMap)
+			total, err = s.sendExistPieces(log, request, sync, getPieces, sentMap)
 			if err != nil {
 				logger.Errorf("send reminding piece tasks error: %s", err)
 				return err
@@ -209,7 +245,7 @@ func (s *server) SyncPieceTasks(sync dfdaemongrpc.Daemon_SyncPieceTasksServer) e
 	if !ok {
 		// running task not found, double check for done task
 		request.StartNum = searchNextPieceNum(sentMap, skipPieceCount)
-		total, err = s.sendExistPieces(log, request, sync, sentMap)
+		total, err = s.sendExistPieces(log, request, sync, getPieces, sentMap)
 		if err != nil {
 			log.Errorf("send exist piece tasks error: %s", err)
 			return err
@@ -231,6 +267,7 @@ func (s *server) SyncPieceTasks(sync dfdaemongrpc.Daemon_SyncPieceTasksServer) e
 		done:                make(chan struct{}),
 		uploadAddr:          s.uploadAddr,
 		SugaredLoggerOnWith: log,
+		attributeSent:       atomic.NewBool(attributeSent),
 	}
 
 	go sub.receiveRemainingPieceTaskRequests()
diff --git a/client/daemon/rpcserver/seeder.go b/client/daemon/rpcserver/seeder.go
index 9cf3dab5..58aaaab5 100644
--- a/client/daemon/rpcserver/seeder.go
+++ b/client/daemon/rpcserver/seeder.go
@@ -66,7 +66,7 @@ func (s *seeder) ObtainSeeds(seedRequest *cdnsystem.SeedRequest, seedsServer cdn
 		},
 		Limit:      0,
 		Callsystem: "",
-		Range:      nil,
+		Range:      nil, // following code will update Range
 	}
 
 	log := logger.With("peer", req.PeerId, "task", seedRequest.TaskId, "component", "seedService")
@@ -125,6 +125,7 @@ type seedSynchronizer struct {
 	seedsServer     cdnsystem.Seeder_ObtainSeedsServer
 	seedTaskRequest *peer.SeedTaskRequest
 	startNanoSecond int64
+	attributeSent   bool
 }
 
 func (s *seedSynchronizer) sendPieceSeeds() (err error) {
@@ -184,6 +185,15 @@ func (s *seedSynchronizer) sendRemindingPieceSeeds(desired int32) error {
 			s.Errorf("get pieces error %s, desired: %d", err.Error(), desired)
 			return err
 		}
+		if !s.attributeSent {
+			exa, err := s.Storage.GetExtendAttribute(s.Context, nil)
+			if err != nil {
+				s.Errorf("get extend attribute error: %s", err.Error())
+				return err
+			}
+			pp.ExtendAttribute = exa
+			s.attributeSent = true
+		}
 
 		for _, p := range pp.PieceInfos {
 			if p.PieceNum != desired {
@@ -229,6 +239,15 @@ func (s *seedSynchronizer) sendOrderedPieceSeeds(desired, orderedNum int32, fini
 			s.Errorf("desired pieces %d not found", cur)
 			return -1, fmt.Errorf("get seed piece %d info failed", cur)
 		}
+		if !s.attributeSent {
+			exa, err := s.Storage.GetExtendAttribute(s.Context, nil)
+			if err != nil {
+				s.Errorf("get extend attribute error: %s", err.Error())
+				return -1, err
+			}
+			pp.ExtendAttribute = exa
+			s.attributeSent = true
+		}
 
 		ps := s.compositePieceSeed(pp, pp.PieceInfos[0])
 		if cur == orderedNum && finished {
diff --git a/client/daemon/rpcserver/subscriber.go b/client/daemon/rpcserver/subscriber.go
index d1c54e75..85597e57 100644
--- a/client/daemon/rpcserver/subscriber.go
+++ b/client/daemon/rpcserver/subscriber.go
@@ -21,6 +21,7 @@ import (
 	"io"
 	"sync"
 
+	"go.uber.org/atomic"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 
@@ -42,11 +43,24 @@ type subscriber struct {
 	sentMap        map[int32]struct{}
 	done           chan struct{}
 	uploadAddr     string
+	attributeSent  *atomic.Bool
 }
 
 func (s *subscriber) getPieces(ctx context.Context, request *base.PieceTaskRequest) (*base.PiecePacket, error) {
 	p, err := s.Storage.GetPieces(ctx, request)
+	if err != nil {
+		return nil, err
+	}
 	p.DstAddr = s.uploadAddr
+	if !s.attributeSent.Load() && len(p.PieceInfos) > 0 {
+		exa, err := s.Storage.GetExtendAttribute(ctx, nil)
+		if err != nil {
+			s.Errorf("get extend attribute error: %s", err.Error())
+			return nil, err
+		}
+		p.ExtendAttribute = exa
+		s.attributeSent.Store(true)
+	}
 	return p, err
 }
 
diff --git a/client/daemon/storage/local_storage.go b/client/daemon/storage/local_storage.go
index 2566b753..366607e3 100644
--- a/client/daemon/storage/local_storage.go
+++ b/client/daemon/storage/local_storage.go
@@ -211,6 +211,10 @@ func (t *localTaskStore) UpdateTask(ctx context.Context, req *UpdateTaskRequest)
 		t.PieceMd5Sign = req.PieceMd5Sign
 		t.Debugf("update piece md5 sign: %s", t.PieceMd5Sign)
 	}
+	if t.Header == nil && req.Header != nil {
+		t.Header = req.Header
+		t.Debugf("update header: %#v", t.Header)
+	}
 	return nil
 }
 
@@ -434,6 +438,23 @@ func (t *localTaskStore) GetTotalPieces(ctx context.Context, req *PeerTaskMetada
 	return t.TotalPieces, nil
 }
 
+func (t *localTaskStore) GetExtendAttribute(ctx context.Context, req *PeerTaskMetadata) (*base.ExtendAttribute, error) {
+	if t.invalid.Load() {
+		t.Errorf("invalid digest, refuse to get total pieces")
+		return nil, ErrInvalidDigest
+	}
+	if t.Header == nil {
+		return nil, nil
+	}
+	hdr := map[string]string{}
+	for k, v := range *t.Header {
+		if len(v) > 0 {
+			hdr[k] = t.Header.Get(k)
+		}
+	}
+	return &base.ExtendAttribute{Header: hdr}, nil
+}
+
 func (t *localTaskStore) CanReclaim() bool {
 	if t.invalid.Load() {
 		return true
diff --git a/client/daemon/storage/local_storage_subtask.go b/client/daemon/storage/local_storage_subtask.go
index e0660f3e..d44eccd5 100644
--- a/client/daemon/storage/local_storage_subtask.go
+++ b/client/daemon/storage/local_storage_subtask.go
@@ -387,3 +387,20 @@ func (t *localSubTaskStore) MarkReclaim() {
 func (t *localSubTaskStore) Reclaim() error {
 	return nil
 }
+
+func (t *localSubTaskStore) GetExtendAttribute(ctx context.Context, req *PeerTaskMetadata) (*base.ExtendAttribute, error) {
+	if t.invalid.Load() {
+		t.Errorf("invalid digest, refuse to get total pieces")
+		return nil, ErrInvalidDigest
+	}
+	if t.Header == nil {
+		return nil, nil
+	}
+	hdr := map[string]string{}
+	for k, v := range *t.Header {
+		if len(v) > 0 {
+			hdr[k] = t.Header.Get(k)
+		}
+	}
+	return &base.ExtendAttribute{Header: hdr}, nil
+}
diff --git a/client/daemon/storage/metadata.go b/client/daemon/storage/metadata.go
index 2dac2429..d6343432 100644
--- a/client/daemon/storage/metadata.go
+++ b/client/daemon/storage/metadata.go
@@ -21,6 +21,7 @@ import (
 
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/pkg/rpc/base"
+	"d7y.io/dragonfly/v2/pkg/source"
 )
 
 type persistentMetadata struct {
@@ -34,6 +35,7 @@ type persistentMetadata struct {
 	PieceMd5Sign  string                  `json:"pieceMd5Sign"`
 	DataFilePath  string                  `json:"dataFilePath"`
 	Done          bool                    `json:"done"`
+	Header        *source.Header          `json:"header"`
 }
 
 type PeerTaskMetadata struct {
@@ -105,6 +107,7 @@ type UpdateTaskRequest struct {
 	ContentLength int64
 	TotalPieces   int32
 	PieceMd5Sign  string
+	Header        *source.Header
 }
 
 type ReusePeerTask = UpdateTaskRequest
diff --git a/client/daemon/storage/storage_manager.go b/client/daemon/storage/storage_manager.go
index 06dc265f..ff4b8607 100644
--- a/client/daemon/storage/storage_manager.go
+++ b/client/daemon/storage/storage_manager.go
@@ -59,6 +59,8 @@ type TaskStorageDriver interface {
 
 	GetTotalPieces(ctx context.Context, req *PeerTaskMetadata) (int32, error)
 
+	GetExtendAttribute(ctx context.Context, req *PeerTaskMetadata) (*base.ExtendAttribute, error)
+
 	UpdateTask(ctx context.Context, req *UpdateTaskRequest) error
 
 	// Store stores task data to the target path
@@ -337,6 +339,18 @@ func (s *storageManager) GetTotalPieces(ctx context.Context, req *PeerTaskMetada
 	return t.(TaskStorageDriver).GetTotalPieces(ctx, req)
 }
 
+func (s *storageManager) GetExtendAttribute(ctx context.Context, req *PeerTaskMetadata) (*base.ExtendAttribute, error) {
+	t, ok := s.LoadTask(
+		PeerTaskMetadata{
+			TaskID: req.TaskID,
+			PeerID: req.PeerID,
+		})
+	if !ok {
+		return nil, ErrTaskNotFound
+	}
+	return t.(TaskStorageDriver).GetExtendAttribute(ctx, req)
+}
+
 func (s *storageManager) LoadTask(meta PeerTaskMetadata) (TaskStorageDriver, bool) {
 	s.Keep()
 	d, ok := s.tasks.Load(meta)
diff --git a/client/daemon/transport/transport.go b/client/daemon/transport/transport.go
index 462ac882..caa0bf42 100644
--- a/client/daemon/transport/transport.go
+++ b/client/daemon/transport/transport.go
@@ -366,7 +366,7 @@ func delHopHeaders(header http.Header) {
 	}
 }
 
-func httpResponse(req *http.Request, status int, body string) (*http.Response, error) {
+func compositeErrorHTTPResponse(req *http.Request, status int, body string) (*http.Response, error) {
 	resp := &http.Response{
 		StatusCode:    status,
 		Body:          io.NopCloser(bytes.NewBufferString(body)),
@@ -380,13 +380,13 @@ func httpResponse(req *http.Request, status int, body string) (*http.Response, e
 }
 
 func badRequest(req *http.Request, body string) (*http.Response, error) {
-	return httpResponse(req, http.StatusBadRequest, body)
+	return compositeErrorHTTPResponse(req, http.StatusBadRequest, body)
 }
 
 func notImplemented(req *http.Request, body string) (*http.Response, error) {
-	return httpResponse(req, http.StatusNotImplemented, body)
+	return compositeErrorHTTPResponse(req, http.StatusNotImplemented, body)
 }
 
 func requestedRangeNotSatisfiable(req *http.Request, body string) (*http.Response, error) {
-	return httpResponse(req, http.StatusRequestedRangeNotSatisfiable, body)
+	return compositeErrorHTTPResponse(req, http.StatusRequestedRangeNotSatisfiable, body)
 }
diff --git a/hack/protoc.sh b/hack/protoc.sh
index e6cfb0e2..fe551dd8 100755
--- a/hack/protoc.sh
+++ b/hack/protoc.sh
@@ -9,7 +9,7 @@ proto_modules="base cdnsystem dfdaemon manager scheduler"
 echo "generate protos..."
 
 for module in ${proto_modules}; do
-  if docker run -v $PWD:/defs ${PROTOC_ALL_IMAGE} \
+  if docker run --rm -v $PWD:/defs ${PROTOC_ALL_IMAGE} \
     -d ${PROTO_PATH}/$module -i . \
     -l ${LANGUAGE} -o . \
     --go-source-relative \
diff --git a/pkg/rpc/base/base.pb.go b/pkg/rpc/base/base.pb.go
index 9b1d2aa3..283328bd 100644
--- a/pkg/rpc/base/base.pb.go
+++ b/pkg/rpc/base/base.pb.go
@@ -650,6 +650,54 @@ func (x *PieceInfo) GetDownloadCost() uint64 {
 	return 0
 }
 
+type ExtendAttribute struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// task response header, eg: HTTP Response Header
+	Header map[string]string `protobuf:"bytes,1,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *ExtendAttribute) Reset() {
+	*x = ExtendAttribute{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_pkg_rpc_base_base_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ExtendAttribute) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ExtendAttribute) ProtoMessage() {}
+
+func (x *ExtendAttribute) ProtoReflect() protoreflect.Message {
+	mi := &file_pkg_rpc_base_base_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ExtendAttribute.ProtoReflect.Descriptor instead.
+func (*ExtendAttribute) Descriptor() ([]byte, []int) {
+	return file_pkg_rpc_base_base_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ExtendAttribute) GetHeader() map[string]string {
+	if x != nil {
+		return x.Header
+	}
+	return nil
+}
+
 type PiecePacket struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -666,12 +714,14 @@ type PiecePacket struct {
 	ContentLength int64 `protobuf:"varint,7,opt,name=content_length,json=contentLength,proto3" json:"content_length,omitempty"`
 	// sha256 code of all piece md5
 	PieceMd5Sign string `protobuf:"bytes,8,opt,name=piece_md5_sign,json=pieceMd5Sign,proto3" json:"piece_md5_sign,omitempty"`
+	// task extend attribute
+	ExtendAttribute *ExtendAttribute `protobuf:"bytes,9,opt,name=extend_attribute,json=extendAttribute,proto3" json:"extend_attribute,omitempty"`
 }
 
 func (x *PiecePacket) Reset() {
 	*x = PiecePacket{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_pkg_rpc_base_base_proto_msgTypes[5]
+		mi := &file_pkg_rpc_base_base_proto_msgTypes[6]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -684,7 +734,7 @@ func (x *PiecePacket) String() string {
 func (*PiecePacket) ProtoMessage() {}
 
 func (x *PiecePacket) ProtoReflect() protoreflect.Message {
-	mi := &file_pkg_rpc_base_base_proto_msgTypes[5]
+	mi := &file_pkg_rpc_base_base_proto_msgTypes[6]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -697,7 +747,7 @@ func (x *PiecePacket) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use PiecePacket.ProtoReflect.Descriptor instead.
 func (*PiecePacket) Descriptor() ([]byte, []int) {
-	return file_pkg_rpc_base_base_proto_rawDescGZIP(), []int{5}
+	return file_pkg_rpc_base_base_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *PiecePacket) GetTaskId() string {
@@ -749,6 +799,13 @@ func (x *PiecePacket) GetPieceMd5Sign() string {
 	return ""
 }
 
+func (x *PiecePacket) GetExtendAttribute() *ExtendAttribute {
+	if x != nil {
+		return x.ExtendAttribute
+	}
+	return nil
+}
+
 var File_pkg_rpc_base_base_proto protoreflect.FileDescriptor
 
 var file_pkg_rpc_base_base_proto_rawDesc = []byte{
@@ -821,75 +878,88 @@ var file_pkg_rpc_base_base_proto_rawDesc = []byte{
 	0x65, 0x53, 0x74, 0x79, 0x6c, 0x65, 0x12, 0x2c, 0x0a, 0x0d, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f,
 	0x61, 0x64, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x42, 0x07, 0xfa,
 	0x42, 0x04, 0x32, 0x02, 0x28, 0x00, 0x52, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64,
-	0x43, 0x6f, 0x73, 0x74, 0x22, 0x95, 0x02, 0x0a, 0x0b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61,
-	0x63, 0x6b, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06,
-	0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x69,
-	0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01,
-	0x52, 0x06, 0x64, 0x73, 0x74, 0x50, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f,
-	0x61, 0x64, 0x64, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72,
-	0x02, 0x10, 0x01, 0x52, 0x07, 0x64, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x30, 0x0a, 0x0b,
-	0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e,
-	0x66, 0x6f, 0x52, 0x0a, 0x70, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x12, 0x1f,
-	0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x18, 0x06, 0x20,
-	0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x69, 0x65, 0x63, 0x65, 0x12,
-	0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74,
-	0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
-	0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f,
-	0x6d, 0x64, 0x35, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
-	0x70, 0x69, 0x65, 0x63, 0x65, 0x4d, 0x64, 0x35, 0x53, 0x69, 0x67, 0x6e, 0x2a, 0xbd, 0x05, 0x0a,
-	0x04, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
-	0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x75, 0x63, 0x63,
-	0x65, 0x73, 0x73, 0x10, 0xc8, 0x01, 0x12, 0x16, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
-	0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x10, 0xf4, 0x03, 0x12, 0x13,
-	0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x63, 0x6b, 0x65, 0x64,
-	0x10, 0xe8, 0x07, 0x12, 0x0f, 0x0a, 0x0a, 0x42, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x10, 0xf8, 0x0a, 0x12, 0x15, 0x0a, 0x10, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b,
-	0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x10, 0xfc, 0x0a, 0x12, 0x11, 0x0a, 0x0c, 0x55,
-	0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0xdc, 0x0b, 0x12, 0x13,
-	0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4f, 0x75, 0x74,
-	0x10, 0xe0, 0x0b, 0x12, 0x10, 0x0a, 0x0b, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x72, 0x72,
-	0x6f, 0x72, 0x10, 0xa0, 0x1f, 0x12, 0x1b, 0x0a, 0x16, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50,
-	0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x10,
-	0xa1, 0x1f, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x68, 0x65,
-	0x64, 0x75, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x10, 0xa2, 0x1f, 0x12, 0x1a,
-	0x0a, 0x15, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x43,
-	0x61, 0x6e, 0x63, 0x65, 0x6c, 0x65, 0x64, 0x10, 0xa3, 0x1f, 0x12, 0x19, 0x0a, 0x14, 0x43, 0x6c,
-	0x69, 0x65, 0x6e, 0x74, 0x57, 0x61, 0x69, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x61,
-	0x64, 0x79, 0x10, 0xa4, 0x1f, 0x12, 0x1c, 0x0a, 0x17, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50,
-	0x69, 0x65, 0x63, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c,
-	0x10, 0xa5, 0x1f, 0x12, 0x1b, 0x0a, 0x16, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x10, 0xa6, 0x1f,
-	0x12, 0x1a, 0x0a, 0x15, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,
-	0x74, 0x69, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0xa7, 0x1f, 0x12, 0x1a, 0x0a, 0x15,
-	0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
-	0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0xa8, 0x1f, 0x12, 0x18, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65,
-	0x6e, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x10,
-	0xb4, 0x22, 0x12, 0x0f, 0x0a, 0x0a, 0x53, 0x63, 0x68, 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72,
-	0x10, 0x88, 0x27, 0x12, 0x18, 0x0a, 0x13, 0x53, 0x63, 0x68, 0x65, 0x64, 0x4e, 0x65, 0x65, 0x64,
-	0x42, 0x61, 0x63, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x10, 0x89, 0x27, 0x12, 0x12, 0x0a,
-	0x0d, 0x53, 0x63, 0x68, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x47, 0x6f, 0x6e, 0x65, 0x10, 0x8a,
-	0x27, 0x12, 0x16, 0x0a, 0x11, 0x53, 0x63, 0x68, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x6f,
-	0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x10, 0x8c, 0x27, 0x12, 0x23, 0x0a, 0x1e, 0x53, 0x63, 0x68,
-	0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c,
-	0x74, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x10, 0x8d, 0x27, 0x12, 0x19,
-	0x0a, 0x14, 0x53, 0x63, 0x68, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x8e, 0x27, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x44, 0x4e,
-	0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0xf0, 0x2e, 0x12, 0x18, 0x0a, 0x13, 0x43, 0x44, 0x4e, 0x54,
-	0x61, 0x73, 0x6b, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x46, 0x61, 0x69, 0x6c, 0x10,
-	0xf1, 0x2e, 0x12, 0x18, 0x0a, 0x13, 0x43, 0x44, 0x4e, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x6f, 0x77,
-	0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x10, 0xf2, 0x2e, 0x12, 0x14, 0x0a, 0x0f,
-	0x43, 0x44, 0x4e, 0x54, 0x61, 0x73, 0x6b, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x10,
-	0x84, 0x32, 0x12, 0x18, 0x0a, 0x13, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x73,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x10, 0xd9, 0x36, 0x2a, 0x17, 0x0a, 0x0a,
-	0x50, 0x69, 0x65, 0x63, 0x65, 0x53, 0x74, 0x79, 0x6c, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x4c,
-	0x41, 0x49, 0x4e, 0x10, 0x00, 0x2a, 0x2c, 0x0a, 0x09, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x63, 0x6f,
-	0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x09,
-	0x0a, 0x05, 0x53, 0x4d, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x49, 0x4e,
-	0x59, 0x10, 0x02, 0x42, 0x22, 0x5a, 0x20, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72,
-	0x61, 0x67, 0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72,
-	0x70, 0x63, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x43, 0x6f, 0x73, 0x74, 0x22, 0x87, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41,
+	0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64,
+	0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e,
+	0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x2e,
+	0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61,
+	0x64, 0x65, 0x72, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74,
+	0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd7,
+	0x02, 0x0a, 0x0b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x20,
+	0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
+	0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64,
+	0x12, 0x20, 0x0a, 0x07, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x64, 0x73, 0x74, 0x50,
+	0x69, 0x64, 0x12, 0x22, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x07, 0x64,
+	0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x30, 0x0a, 0x0b, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f,
+	0x69, 0x6e, 0x66, 0x6f, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61,
+	0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0a, 0x70, 0x69,
+	0x65, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61,
+	0x6c, 0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74,
+	0x6f, 0x74, 0x61, 0x6c, 0x50, 0x69, 0x65, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e,
+	0x74, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28,
+	0x03, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68,
+	0x12, 0x24, 0x0a, 0x0e, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x6d, 0x64, 0x35, 0x5f, 0x73, 0x69,
+	0x67, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x69, 0x65, 0x63, 0x65, 0x4d,
+	0x64, 0x35, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x40, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64,
+	0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x15, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41, 0x74,
+	0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41,
+	0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x2a, 0xbd, 0x05, 0x0a, 0x04, 0x43, 0x6f, 0x64,
+	0x65, 0x12, 0x11, 0x0a, 0x0d, 0x58, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
+	0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x07, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x10,
+	0xc8, 0x01, 0x12, 0x16, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x6e, 0x61, 0x76,
+	0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x10, 0xf4, 0x03, 0x12, 0x13, 0x0a, 0x0e, 0x52, 0x65,
+	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x10, 0xe8, 0x07, 0x12,
+	0x0f, 0x0a, 0x0a, 0x42, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x10, 0xf8, 0x0a,
+	0x12, 0x15, 0x0a, 0x10, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x4e, 0x6f, 0x74, 0x46,
+	0x6f, 0x75, 0x6e, 0x64, 0x10, 0xfc, 0x0a, 0x12, 0x11, 0x0a, 0x0c, 0x55, 0x6e, 0x6b, 0x6e, 0x6f,
+	0x77, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0xdc, 0x0b, 0x12, 0x13, 0x0a, 0x0e, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x4f, 0x75, 0x74, 0x10, 0xe0, 0x0b, 0x12,
+	0x10, 0x0a, 0x0b, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0xa0,
+	0x1f, 0x12, 0x1b, 0x0a, 0x16, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x10, 0xa1, 0x1f, 0x12, 0x1a,
+	0x0a, 0x15, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65,
+	0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x10, 0xa2, 0x1f, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x6c,
+	0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x43, 0x61, 0x6e, 0x63, 0x65,
+	0x6c, 0x65, 0x64, 0x10, 0xa3, 0x1f, 0x12, 0x19, 0x0a, 0x14, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,
+	0x57, 0x61, 0x69, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x10, 0xa4,
+	0x1f, 0x12, 0x1c, 0x0a, 0x17, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65,
+	0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x10, 0xa5, 0x1f, 0x12,
+	0x1b, 0x0a, 0x16, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x4c, 0x69, 0x6d, 0x69, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x10, 0xa6, 0x1f, 0x12, 0x1a, 0x0a, 0x15,
+	0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+	0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0xa7, 0x1f, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x6c, 0x69, 0x65,
+	0x6e, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x72, 0x72, 0x6f,
+	0x72, 0x10, 0xa8, 0x1f, 0x12, 0x18, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x69,
+	0x65, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x10, 0xb4, 0x22, 0x12, 0x0f,
+	0x0a, 0x0a, 0x53, 0x63, 0x68, 0x65, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x88, 0x27, 0x12,
+	0x18, 0x0a, 0x13, 0x53, 0x63, 0x68, 0x65, 0x64, 0x4e, 0x65, 0x65, 0x64, 0x42, 0x61, 0x63, 0x6b,
+	0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x10, 0x89, 0x27, 0x12, 0x12, 0x0a, 0x0d, 0x53, 0x63, 0x68,
+	0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x47, 0x6f, 0x6e, 0x65, 0x10, 0x8a, 0x27, 0x12, 0x16, 0x0a,
+	0x11, 0x53, 0x63, 0x68, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75,
+	0x6e, 0x64, 0x10, 0x8c, 0x27, 0x12, 0x23, 0x0a, 0x1e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x50, 0x65,
+	0x65, 0x72, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x65, 0x70,
+	0x6f, 0x72, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x10, 0x8d, 0x27, 0x12, 0x19, 0x0a, 0x14, 0x53, 0x63,
+	0x68, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x72, 0x72,
+	0x6f, 0x72, 0x10, 0x8e, 0x27, 0x12, 0x0d, 0x0a, 0x08, 0x43, 0x44, 0x4e, 0x45, 0x72, 0x72, 0x6f,
+	0x72, 0x10, 0xf0, 0x2e, 0x12, 0x18, 0x0a, 0x13, 0x43, 0x44, 0x4e, 0x54, 0x61, 0x73, 0x6b, 0x52,
+	0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x46, 0x61, 0x69, 0x6c, 0x10, 0xf1, 0x2e, 0x12, 0x18,
+	0x0a, 0x13, 0x43, 0x44, 0x4e, 0x54, 0x61, 0x73, 0x6b, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61,
+	0x64, 0x46, 0x61, 0x69, 0x6c, 0x10, 0xf2, 0x2e, 0x12, 0x14, 0x0a, 0x0f, 0x43, 0x44, 0x4e, 0x54,
+	0x61, 0x73, 0x6b, 0x4e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x10, 0x84, 0x32, 0x12, 0x18,
+	0x0a, 0x13, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x54, 0x79, 0x70, 0x65, 0x10, 0xd9, 0x36, 0x2a, 0x17, 0x0a, 0x0a, 0x50, 0x69, 0x65, 0x63,
+	0x65, 0x53, 0x74, 0x79, 0x6c, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x10,
+	0x00, 0x2a, 0x2c, 0x0a, 0x09, 0x53, 0x69, 0x7a, 0x65, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x0a,
+	0x0a, 0x06, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x4d,
+	0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x54, 0x49, 0x4e, 0x59, 0x10, 0x02, 0x42,
+	0x22, 0x5a, 0x20, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e,
+	0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x62,
+	0x61, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -905,7 +975,7 @@ func file_pkg_rpc_base_base_proto_rawDescGZIP() []byte {
 }
 
 var file_pkg_rpc_base_base_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
-var file_pkg_rpc_base_base_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+var file_pkg_rpc_base_base_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
 var file_pkg_rpc_base_base_proto_goTypes = []interface{}{
 	(Code)(0),                // 0: base.Code
 	(PieceStyle)(0),          // 1: base.PieceStyle
@@ -915,19 +985,23 @@ var file_pkg_rpc_base_base_proto_goTypes = []interface{}{
 	(*HostLoad)(nil),         // 5: base.HostLoad
 	(*PieceTaskRequest)(nil), // 6: base.PieceTaskRequest
 	(*PieceInfo)(nil),        // 7: base.PieceInfo
-	(*PiecePacket)(nil),      // 8: base.PiecePacket
-	nil,                      // 9: base.UrlMeta.HeaderEntry
+	(*ExtendAttribute)(nil),  // 8: base.ExtendAttribute
+	(*PiecePacket)(nil),      // 9: base.PiecePacket
+	nil,                      // 10: base.UrlMeta.HeaderEntry
+	nil,                      // 11: base.ExtendAttribute.HeaderEntry
 }
 var file_pkg_rpc_base_base_proto_depIdxs = []int32{
-	0, // 0: base.GrpcDfError.code:type_name -> base.Code
-	9, // 1: base.UrlMeta.header:type_name -> base.UrlMeta.HeaderEntry
-	1, // 2: base.PieceInfo.piece_style:type_name -> base.PieceStyle
-	7, // 3: base.PiecePacket.piece_infos:type_name -> base.PieceInfo
-	4, // [4:4] is the sub-list for method output_type
-	4, // [4:4] is the sub-list for method input_type
-	4, // [4:4] is the sub-list for extension type_name
-	4, // [4:4] is the sub-list for extension extendee
-	0, // [0:4] is the sub-list for field type_name
+	0,  // 0: base.GrpcDfError.code:type_name -> base.Code
+	10, // 1: base.UrlMeta.header:type_name -> base.UrlMeta.HeaderEntry
+	1,  // 2: base.PieceInfo.piece_style:type_name -> base.PieceStyle
+	11, // 3: base.ExtendAttribute.header:type_name -> base.ExtendAttribute.HeaderEntry
+	7,  // 4: base.PiecePacket.piece_infos:type_name -> base.PieceInfo
+	8,  // 5: base.PiecePacket.extend_attribute:type_name -> base.ExtendAttribute
+	6,  // [6:6] is the sub-list for method output_type
+	6,  // [6:6] is the sub-list for method input_type
+	6,  // [6:6] is the sub-list for extension type_name
+	6,  // [6:6] is the sub-list for extension extendee
+	0,  // [0:6] is the sub-list for field type_name
 }
 
 func init() { file_pkg_rpc_base_base_proto_init() }
@@ -997,6 +1071,18 @@ func file_pkg_rpc_base_base_proto_init() {
 			}
 		}
 		file_pkg_rpc_base_base_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ExtendAttribute); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_pkg_rpc_base_base_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*PiecePacket); i {
 			case 0:
 				return &v.state
@@ -1015,7 +1101,7 @@ func file_pkg_rpc_base_base_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_pkg_rpc_base_base_proto_rawDesc,
 			NumEnums:      3,
-			NumMessages:   7,
+			NumMessages:   9,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/pkg/rpc/base/base.pb.validate.go b/pkg/rpc/base/base.pb.validate.go
index 9c28d449..007c4ea9 100644
--- a/pkg/rpc/base/base.pb.validate.go
+++ b/pkg/rpc/base/base.pb.validate.go
@@ -492,6 +492,73 @@ var _ interface {
 
 var _PieceInfo_PieceMd5_Pattern = regexp.MustCompile("([a-f\\d]{32}|[A-F\\d]{32}|[a-f\\d]{16}|[A-F\\d]{16})")
 
+// Validate checks the field values on ExtendAttribute with the rules defined
+// in the proto definition for this message. If any rules are violated, an
+// error is returned.
+func (m *ExtendAttribute) Validate() error {
+	if m == nil {
+		return nil
+	}
+
+	// no validation rules for Header
+
+	return nil
+}
+
+// ExtendAttributeValidationError is the validation error returned by
+// ExtendAttribute.Validate if the designated constraints aren't met.
+type ExtendAttributeValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e ExtendAttributeValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e ExtendAttributeValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e ExtendAttributeValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e ExtendAttributeValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e ExtendAttributeValidationError) ErrorName() string { return "ExtendAttributeValidationError" }
+
+// Error satisfies the builtin error interface
+func (e ExtendAttributeValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sExtendAttribute.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = ExtendAttributeValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = ExtendAttributeValidationError{}
+
 // Validate checks the field values on PiecePacket with the rules defined in
 // the proto definition for this message. If any rules are violated, an error
 // is returned.
@@ -542,6 +609,16 @@ func (m *PiecePacket) Validate() error {
 
 	// no validation rules for PieceMd5Sign
 
+	if v, ok := interface{}(m.GetExtendAttribute()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return PiecePacketValidationError{
+				field:  "ExtendAttribute",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
 	return nil
 }
 
diff --git a/pkg/rpc/base/base.proto b/pkg/rpc/base/base.proto
index 1b40248a..eec41db3 100644
--- a/pkg/rpc/base/base.proto
+++ b/pkg/rpc/base/base.proto
@@ -130,6 +130,15 @@ message PieceInfo{
   uint64 download_cost = 7 [(validate.rules).uint64.gte = 0];
 }
 
+message ExtendAttribute{
+  // task response header, eg: HTTP Response Header
+  map<string, string> header = 1;
+  // task response code, eg: HTTP Status Code
+  //int32 code = 2;
+  // task response status, eg: HTTP Status
+  //string status = 3;
+}
+
 message PiecePacket{
   string task_id = 2 [(validate.rules).string.min_len = 1];
   string dst_pid = 3 [(validate.rules).string.min_len = 1];
@@ -142,4 +151,6 @@ message PiecePacket{
   int64 content_length = 7;
   // sha256 code of all piece md5
   string piece_md5_sign = 8;
+  // task extend attribute
+  ExtendAttribute extend_attribute = 9;
 }
diff --git a/pkg/rpc/cdnsystem/cdnsystem.pb.go b/pkg/rpc/cdnsystem/cdnsystem.pb.go
index 7d3bb433..75f1cfc6 100644
--- a/pkg/rpc/cdnsystem/cdnsystem.pb.go
+++ b/pkg/rpc/cdnsystem/cdnsystem.pb.go
@@ -126,6 +126,8 @@ type PieceSeed struct {
 	BeginTime uint64 `protobuf:"varint,8,opt,name=begin_time,json=beginTime,proto3" json:"begin_time,omitempty"`
 	// end time for the piece downloading
 	EndTime uint64 `protobuf:"varint,9,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"`
+	// task extend attribute
+	ExtendAttribute *base.ExtendAttribute `protobuf:"bytes,10,opt,name=extend_attribute,json=extendAttribute,proto3" json:"extend_attribute,omitempty"`
 }
 
 func (x *PieceSeed) Reset() {
@@ -216,6 +218,13 @@ func (x *PieceSeed) GetEndTime() uint64 {
 	return 0
 }
 
+func (x *PieceSeed) GetExtendAttribute() *base.ExtendAttribute {
+	if x != nil {
+		return x.ExtendAttribute
+	}
+	return nil
+}
+
 var File_pkg_rpc_cdnsystem_cdnsystem_proto protoreflect.FileDescriptor
 
 var file_pkg_rpc_cdnsystem_cdnsystem_proto_rawDesc = []byte{
@@ -232,7 +241,7 @@ var file_pkg_rpc_cdnsystem_cdnsystem_proto_rawDesc = []byte{
 	0xfa, 0x42, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x28, 0x0a,
 	0x08, 0x75, 0x72, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
 	0x0d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x55, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07,
-	0x75, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x22, 0xa0, 0x02, 0x0a, 0x09, 0x50, 0x69, 0x65, 0x63,
+	0x75, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x22, 0xe2, 0x02, 0x0a, 0x09, 0x50, 0x69, 0x65, 0x63,
 	0x65, 0x53, 0x65, 0x65, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64,
 	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52,
 	0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f,
@@ -250,23 +259,27 @@ var file_pkg_rpc_cdnsystem_cdnsystem_proto_rawDesc = []byte{
 	0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08,
 	0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12,
 	0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x32, 0xc4, 0x01, 0x0a, 0x06, 0x53,
-	0x65, 0x65, 0x64, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0b, 0x4f, 0x62, 0x74, 0x61, 0x69, 0x6e, 0x53,
-	0x65, 0x65, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x63, 0x64, 0x6e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
-	0x2e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x63,
-	0x64, 0x6e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x53, 0x65,
-	0x65, 0x64, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65,
-	0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65,
-	0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e,
-	0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
-	0x12, 0x3f, 0x0a, 0x0e, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x69, 0x65, 0x63, 0x65, 0x54, 0x61, 0x73,
-	0x6b, 0x73, 0x12, 0x16, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x54,
-	0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x62, 0x61, 0x73,
-	0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x28, 0x01, 0x30,
-	0x01, 0x42, 0x27, 0x5a, 0x25, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72, 0x61, 0x67,
-	0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63,
-	0x2f, 0x63, 0x64, 0x6e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
+	0x04, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x10, 0x65, 0x78,
+	0x74, 0x65, 0x6e, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x0a,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x65,
+	0x6e, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0f, 0x65, 0x78, 0x74,
+	0x65, 0x6e, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x32, 0xc4, 0x01, 0x0a,
+	0x06, 0x53, 0x65, 0x65, 0x64, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0b, 0x4f, 0x62, 0x74, 0x61, 0x69,
+	0x6e, 0x53, 0x65, 0x65, 0x64, 0x73, 0x12, 0x16, 0x2e, 0x63, 0x64, 0x6e, 0x73, 0x79, 0x73, 0x74,
+	0x65, 0x6d, 0x2e, 0x53, 0x65, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14,
+	0x2e, 0x63, 0x64, 0x6e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65,
+	0x53, 0x65, 0x65, 0x64, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x50, 0x69, 0x65,
+	0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50,
+	0x69, 0x65, 0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x11, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b,
+	0x65, 0x74, 0x12, 0x3f, 0x0a, 0x0e, 0x53, 0x79, 0x6e, 0x63, 0x50, 0x69, 0x65, 0x63, 0x65, 0x54,
+	0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63,
+	0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x62,
+	0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x28,
+	0x01, 0x30, 0x01, 0x42, 0x27, 0x5a, 0x25, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72,
+	0x61, 0x67, 0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72,
+	0x70, 0x63, 0x2f, 0x63, 0x64, 0x6e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -287,23 +300,25 @@ var file_pkg_rpc_cdnsystem_cdnsystem_proto_goTypes = []interface{}{
 	(*PieceSeed)(nil),             // 1: cdnsystem.PieceSeed
 	(*base.UrlMeta)(nil),          // 2: base.UrlMeta
 	(*base.PieceInfo)(nil),        // 3: base.PieceInfo
-	(*base.PieceTaskRequest)(nil), // 4: base.PieceTaskRequest
-	(*base.PiecePacket)(nil),      // 5: base.PiecePacket
+	(*base.ExtendAttribute)(nil),  // 4: base.ExtendAttribute
+	(*base.PieceTaskRequest)(nil), // 5: base.PieceTaskRequest
+	(*base.PiecePacket)(nil),      // 6: base.PiecePacket
 }
 var file_pkg_rpc_cdnsystem_cdnsystem_proto_depIdxs = []int32{
 	2, // 0: cdnsystem.SeedRequest.url_meta:type_name -> base.UrlMeta
 	3, // 1: cdnsystem.PieceSeed.piece_info:type_name -> base.PieceInfo
-	0, // 2: cdnsystem.Seeder.ObtainSeeds:input_type -> cdnsystem.SeedRequest
-	4, // 3: cdnsystem.Seeder.GetPieceTasks:input_type -> base.PieceTaskRequest
-	4, // 4: cdnsystem.Seeder.SyncPieceTasks:input_type -> base.PieceTaskRequest
-	1, // 5: cdnsystem.Seeder.ObtainSeeds:output_type -> cdnsystem.PieceSeed
-	5, // 6: cdnsystem.Seeder.GetPieceTasks:output_type -> base.PiecePacket
-	5, // 7: cdnsystem.Seeder.SyncPieceTasks:output_type -> base.PiecePacket
-	5, // [5:8] is the sub-list for method output_type
-	2, // [2:5] is the sub-list for method input_type
-	2, // [2:2] is the sub-list for extension type_name
-	2, // [2:2] is the sub-list for extension extendee
-	0, // [0:2] is the sub-list for field type_name
+	4, // 2: cdnsystem.PieceSeed.extend_attribute:type_name -> base.ExtendAttribute
+	0, // 3: cdnsystem.Seeder.ObtainSeeds:input_type -> cdnsystem.SeedRequest
+	5, // 4: cdnsystem.Seeder.GetPieceTasks:input_type -> base.PieceTaskRequest
+	5, // 5: cdnsystem.Seeder.SyncPieceTasks:input_type -> base.PieceTaskRequest
+	1, // 6: cdnsystem.Seeder.ObtainSeeds:output_type -> cdnsystem.PieceSeed
+	6, // 7: cdnsystem.Seeder.GetPieceTasks:output_type -> base.PiecePacket
+	6, // 8: cdnsystem.Seeder.SyncPieceTasks:output_type -> base.PiecePacket
+	6, // [6:9] is the sub-list for method output_type
+	3, // [3:6] is the sub-list for method input_type
+	3, // [3:3] is the sub-list for extension type_name
+	3, // [3:3] is the sub-list for extension extendee
+	0, // [0:3] is the sub-list for field type_name
 }
 
 func init() { file_pkg_rpc_cdnsystem_cdnsystem_proto_init() }
diff --git a/pkg/rpc/cdnsystem/cdnsystem.pb.validate.go b/pkg/rpc/cdnsystem/cdnsystem.pb.validate.go
index dcfa240e..9c2da34e 100644
--- a/pkg/rpc/cdnsystem/cdnsystem.pb.validate.go
+++ b/pkg/rpc/cdnsystem/cdnsystem.pb.validate.go
@@ -169,6 +169,16 @@ func (m *PieceSeed) Validate() error {
 
 	// no validation rules for EndTime
 
+	if v, ok := interface{}(m.GetExtendAttribute()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return PieceSeedValidationError{
+				field:  "ExtendAttribute",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
 	return nil
 }
 
diff --git a/pkg/rpc/cdnsystem/cdnsystem.proto b/pkg/rpc/cdnsystem/cdnsystem.proto
index 6e4ef67f..54be1f8e 100644
--- a/pkg/rpc/cdnsystem/cdnsystem.proto
+++ b/pkg/rpc/cdnsystem/cdnsystem.proto
@@ -48,6 +48,8 @@ message PieceSeed{
   uint64 begin_time = 8;
   // end time for the piece downloading
   uint64 end_time = 9;
+  // task extend attribute
+  base.ExtendAttribute extend_attribute = 10;
 }
 
 // CDN System RPC Service
diff --git a/pkg/rpc/scheduler/scheduler.pb.go b/pkg/rpc/scheduler/scheduler.pb.go
index 2080a7bd..7a34328b 100644
--- a/pkg/rpc/scheduler/scheduler.pb.go
+++ b/pkg/rpc/scheduler/scheduler.pb.go
@@ -211,6 +211,8 @@ type RegisterResult struct {
 	//	*RegisterResult_SinglePiece
 	//	*RegisterResult_PieceContent
 	DirectPiece isRegisterResult_DirectPiece `protobuf_oneof:"direct_piece"`
+	// task extend attribute, only direct_piece will carry extend attribute
+	ExtendAttribute *base.ExtendAttribute `protobuf:"bytes,6,opt,name=extend_attribute,json=extendAttribute,proto3" json:"extend_attribute,omitempty"`
 }
 
 func (x *RegisterResult) Reset() {
@@ -280,6 +282,13 @@ func (x *RegisterResult) GetPieceContent() []byte {
 	return nil
 }
 
+func (x *RegisterResult) GetExtendAttribute() *base.ExtendAttribute {
+	if x != nil {
+		return x.ExtendAttribute
+	}
+	return nil
+}
+
 type isRegisterResult_DirectPiece interface {
 	isRegisterResult_DirectPiece()
 }
@@ -509,6 +518,8 @@ type PieceResult struct {
 	HostLoad *base.HostLoad `protobuf:"bytes,9,opt,name=host_load,json=hostLoad,proto3" json:"host_load,omitempty"`
 	// currently completed piece count, -1 represent download failed
 	FinishedCount int32 `protobuf:"varint,10,opt,name=finished_count,json=finishedCount,proto3" json:"finished_count,omitempty"`
+	// task extend attribute, only first success back source piece will carry extend attribute
+	ExtendAttribute *base.ExtendAttribute `protobuf:"bytes,11,opt,name=extend_attribute,json=extendAttribute,proto3" json:"extend_attribute,omitempty"`
 }
 
 func (x *PieceResult) Reset() {
@@ -613,6 +624,13 @@ func (x *PieceResult) GetFinishedCount() int32 {
 	return 0
 }
 
+func (x *PieceResult) GetExtendAttribute() *base.ExtendAttribute {
+	if x != nil {
+		return x.ExtendAttribute
+	}
+	return nil
+}
+
 type PeerPacket struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1240,7 +1258,7 @@ var file_pkg_rpc_scheduler_scheduler_proto_rawDesc = []byte{
 	0x61, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x2c, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e,
 	0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
 	0x65, 0x72, 0x2e, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x52, 0x07, 0x70, 0x61, 0x74, 0x74,
-	0x65, 0x72, 0x6e, 0x22, 0xe0, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
+	0x65, 0x72, 0x6e, 0x22, 0xa2, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
 	0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69,
 	0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01,
 	0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x0a, 0x73, 0x69, 0x7a, 0x65,
@@ -1253,183 +1271,191 @@ var file_pkg_rpc_scheduler_scheduler_proto_rawDesc = []byte{
 	0x48, 0x00, 0x52, 0x0b, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x50, 0x69, 0x65, 0x63, 0x65, 0x12,
 	0x25, 0x0a, 0x0d, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
 	0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x0c, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43,
-	0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
-	0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x22, 0x83, 0x01, 0x0a, 0x0b, 0x53, 0x69, 0x6e, 0x67, 0x6c,
-	0x65, 0x50, 0x69, 0x65, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x07, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x69,
+	0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64,
+	0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x15, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41, 0x74,
+	0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41,
+	0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x64, 0x69, 0x72, 0x65,
+	0x63, 0x74, 0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x22, 0x83, 0x01, 0x0a, 0x0b, 0x53, 0x69, 0x6e,
+	0x67, 0x6c, 0x65, 0x50, 0x69, 0x65, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x07, 0x64, 0x73, 0x74, 0x5f,
+	0x70, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02,
+	0x10, 0x01, 0x52, 0x06, 0x64, 0x73, 0x74, 0x50, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x08, 0x64, 0x73,
+	0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42,
+	0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x07, 0x64, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x2e,
+	0x0a, 0x0a, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0xb0,
+	0x02, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69,
 	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01,
-	0x52, 0x06, 0x64, 0x73, 0x74, 0x50, 0x69, 0x64, 0x12, 0x22, 0x0a, 0x08, 0x64, 0x73, 0x74, 0x5f,
-	0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72,
-	0x02, 0x10, 0x01, 0x52, 0x07, 0x64, 0x73, 0x74, 0x41, 0x64, 0x64, 0x72, 0x12, 0x2e, 0x0a, 0x0a,
-	0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66,
-	0x6f, 0x52, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0xb0, 0x02, 0x0a,
-	0x08, 0x50, 0x65, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x02,
-	0x69, 0x64, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07,
-	0xfa, 0x42, 0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x02, 0x69, 0x70, 0x12, 0x27, 0x0a, 0x08, 0x72,
-	0x70, 0x63, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0c, 0xfa,
-	0x42, 0x09, 0x1a, 0x07, 0x10, 0xff, 0xff, 0x03, 0x28, 0x80, 0x08, 0x52, 0x07, 0x72, 0x70, 0x63,
-	0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x09, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x70, 0x6f, 0x72,
-	0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0c, 0xfa, 0x42, 0x09, 0x1a, 0x07, 0x10, 0xff,
-	0xff, 0x03, 0x28, 0x80, 0x08, 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12,
-	0x24, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x68, 0x01, 0x52, 0x08, 0x68, 0x6f, 0x73,
-	0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
-	0x79, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e,
-	0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a,
-	0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64,
-	0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x63, 0x12, 0x21, 0x0a, 0x0c,
-	0x6e, 0x65, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x22,
-	0xe2, 0x02, 0x0a, 0x0b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12,
-	0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49,
-	0x64, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x72, 0x63, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x73, 0x72, 0x63,
-	0x50, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x73, 0x74, 0x50, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x0a,
-	0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66,
-	0x6f, 0x52, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a,
-	0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x09, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65,
-	0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x65,
-	0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73,
-	0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
-	0x12, 0x1e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a,
-	0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65,
-	0x12, 0x2b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x09, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4c,
-	0x6f, 0x61, 0x64, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x12, 0x25, 0x0a,
-	0x0e, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
-	0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x43,
-	0x6f, 0x75, 0x6e, 0x74, 0x22, 0x8e, 0x03, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x50, 0x61, 0x63,
-	0x6b, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74,
-	0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x72, 0x63, 0x5f, 0x70, 0x69, 0x64,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52,
-	0x06, 0x73, 0x72, 0x63, 0x50, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x61, 0x6c,
-	0x6c, 0x65, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42,
-	0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c,
-	0x65, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x09, 0x6d, 0x61, 0x69, 0x6e, 0x5f,
-	0x70, 0x65, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x63, 0x68,
-	0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x50, 0x61, 0x63, 0x6b, 0x65,
-	0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x08, 0x6d, 0x61, 0x69, 0x6e,
-	0x50, 0x65, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x0b, 0x73, 0x74, 0x65, 0x61, 0x6c, 0x5f, 0x70, 0x65,
-	0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x63, 0x68, 0x65,
-	0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
-	0x2e, 0x44, 0x65, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x0a, 0x73, 0x74, 0x65, 0x61, 0x6c,
-	0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20,
-	0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52,
-	0x04, 0x63, 0x6f, 0x64, 0x65, 0x1a, 0x6e, 0x0a, 0x08, 0x44, 0x65, 0x73, 0x74, 0x50, 0x65, 0x65,
-	0x72, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa,
-	0x42, 0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x02, 0x69, 0x70, 0x12, 0x27, 0x0a, 0x08, 0x72, 0x70,
-	0x63, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0c, 0xfa, 0x42,
-	0x09, 0x1a, 0x07, 0x10, 0xff, 0xff, 0x03, 0x28, 0x80, 0x08, 0x52, 0x07, 0x72, 0x70, 0x63, 0x50,
-	0x6f, 0x72, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x70,
-	0x65, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa6, 0x03, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65,
-	0x73, 0x75, 0x6c, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06,
-	0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69,
+	0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x02, 0x69, 0x70, 0x12, 0x27, 0x0a,
+	0x08, 0x72, 0x70, 0x63, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x42,
+	0x0c, 0xfa, 0x42, 0x09, 0x1a, 0x07, 0x10, 0xff, 0xff, 0x03, 0x28, 0x80, 0x08, 0x52, 0x07, 0x72,
+	0x70, 0x63, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x09, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x70,
+	0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42, 0x0c, 0xfa, 0x42, 0x09, 0x1a, 0x07,
+	0x10, 0xff, 0xff, 0x03, 0x28, 0x80, 0x08, 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x50, 0x6f, 0x72,
+	0x74, 0x12, 0x24, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x68, 0x01, 0x52, 0x08, 0x68,
+	0x6f, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x63, 0x75, 0x72,
+	0x69, 0x74, 0x79, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
+	0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03,
+	0x69, 0x64, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x63, 0x12, 0x21,
+	0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x5f, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x18, 0x09,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67,
+	0x79, 0x22, 0xa4, 0x03, 0x0a, 0x0b, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c,
+	0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73,
+	0x6b, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x72, 0x63, 0x5f, 0x70, 0x69, 0x64, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x73,
+	0x72, 0x63, 0x50, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x73, 0x74, 0x5f, 0x70, 0x69, 0x64,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x73, 0x74, 0x50, 0x69, 0x64, 0x12, 0x2e,
+	0x0a, 0x0a, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d,
+	0x0a, 0x0a, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x09, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x19, 0x0a,
+	0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63,
+	0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65,
+	0x73, 0x73, 0x12, 0x1e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x0a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f,
+	0x64, 0x65, 0x12, 0x2b, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x18,
+	0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x48, 0x6f, 0x73,
+	0x74, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x4c, 0x6f, 0x61, 0x64, 0x12,
+	0x25, 0x0a, 0x0e, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65,
+	0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x40, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64,
+	0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x15, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41, 0x74,
+	0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x41,
+	0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x22, 0x8e, 0x03, 0x0a, 0x0a, 0x50, 0x65, 0x65,
+	0x72, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f,
+	0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10,
+	0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x73, 0x72, 0x63,
+	0x5f, 0x70, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72,
+	0x02, 0x10, 0x01, 0x52, 0x06, 0x73, 0x72, 0x63, 0x50, 0x69, 0x64, 0x12, 0x2e, 0x0a, 0x0e, 0x70,
+	0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x0d, 0x70, 0x61,
+	0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x09, 0x6d,
+	0x61, 0x69, 0x6e, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e,
+	0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x50,
+	0x61, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x08,
+	0x6d, 0x61, 0x69, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x0b, 0x73, 0x74, 0x65, 0x61,
+	0x6c, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e,
+	0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x50, 0x61,
+	0x63, 0x6b, 0x65, 0x74, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x0a, 0x73,
+	0x74, 0x65, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x0a, 0x04, 0x63, 0x6f, 0x64,
+	0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x43,
+	0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x1a, 0x6e, 0x0a, 0x08, 0x44, 0x65, 0x73,
+	0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x02, 0x69, 0x70, 0x12, 0x27,
+	0x0a, 0x08, 0x72, 0x70, 0x63, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
+	0x42, 0x0c, 0xfa, 0x42, 0x09, 0x1a, 0x07, 0x10, 0xff, 0xff, 0x03, 0x28, 0x80, 0x08, 0x52, 0x07,
+	0x72, 0x70, 0x63, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f,
+	0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10,
+	0x01, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x22, 0xa6, 0x03, 0x0a, 0x0a, 0x50, 0x65,
+	0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b,
+	0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02,
+	0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x70, 0x65,
+	0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04,
+	0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x06,
+	0x73, 0x72, 0x63, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42,
+	0x04, 0x72, 0x02, 0x70, 0x01, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x70, 0x12, 0x27, 0x0a, 0x0f,
+	0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44,
+	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x63, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x03, 0x69, 0x64, 0x63, 0x12, 0x1a, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x06,
+	0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x03,
+	0x75, 0x72, 0x6c, 0x12, 0x37, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x6c,
+	0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x10, 0xfa, 0x42, 0x0d,
+	0x22, 0x0b, 0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x52, 0x0d, 0x63,
+	0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07,
+	0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74,
+	0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x09,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75,
+	0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63,
+	0x63, 0x65, 0x73, 0x73, 0x12, 0x1e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01,
+	0x28, 0x0e, 0x32, 0x0a, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04,
+	0x63, 0x6f, 0x64, 0x65, 0x12, 0x3c, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x69,
+	0x65, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x42,
+	0x10, 0xfa, 0x42, 0x0d, 0x1a, 0x0b, 0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0x01, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x69, 0x65, 0x63, 0x65, 0x43, 0x6f, 0x75,
+	0x6e, 0x74, 0x22, 0x50, 0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74,
+	0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b,
+	0x49, 0x64, 0x12, 0x20, 0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x70, 0x65,
+	0x65, 0x72, 0x49, 0x64, 0x22, 0x33, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f,
+	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10,
+	0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x94, 0x02, 0x0a, 0x04, 0x54, 0x61,
+	0x73, 0x6b, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07,
+	0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x04, 0x74,
+	0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02,
+	0x28, 0x00, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74,
+	0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
+	0x42, 0x07, 0xfa, 0x42, 0x04, 0x22, 0x02, 0x28, 0x01, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65,
+	0x6e, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61,
+	0x6c, 0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x0f, 0x74, 0x6f,
+	0x74, 0x61, 0x6c, 0x50, 0x69, 0x65, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a,
+	0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42,
+	0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0a,
+	0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05,
+	0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x43,
+	0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x68, 0x61, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c,
+	0x61, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10,
+	0x68, 0x61, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x65, 0x72,
+	0x22, 0xf8, 0x01, 0x0a, 0x13, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54, 0x61, 0x73,
+	0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b,
+	0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02,
+	0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x03, 0x63, 0x69,
 	0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01,
-	0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x06, 0x73, 0x72, 0x63, 0x5f,
-	0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x70,
-	0x01, 0x52, 0x05, 0x73, 0x72, 0x63, 0x49, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x63, 0x75,
-	0x72, 0x69, 0x74, 0x79, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
-	0x69, 0x64, 0x63, 0x12, 0x1a, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
-	0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0x88, 0x01, 0x01, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12,
-	0x37, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74,
-	0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x42, 0x10, 0xfa, 0x42, 0x0d, 0x22, 0x0b, 0x28, 0xff,
-	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65,
-	0x6e, 0x74, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x61, 0x66,
-	0x66, 0x69, 0x63, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x74, 0x72, 0x61, 0x66, 0x66,
-	0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d,
-	0x52, 0x04, 0x63, 0x6f, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73,
-	0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
-	0x12, 0x1e, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a,
-	0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65,
-	0x12, 0x3c, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f,
-	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x42, 0x10, 0xfa, 0x42, 0x0d,
-	0x1a, 0x0b, 0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x52, 0x0f, 0x74,
-	0x6f, 0x74, 0x61, 0x6c, 0x50, 0x69, 0x65, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x50,
-	0x0a, 0x0a, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x07,
-	0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa,
-	0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x20,
-	0x0a, 0x07, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42,
-	0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x70, 0x65, 0x65, 0x72, 0x49, 0x64,
-	0x22, 0x33, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06, 0x74,
-	0x61, 0x73, 0x6b, 0x49, 0x64, 0x22, 0x94, 0x02, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x17,
-	0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72,
-	0x02, 0x10, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x04,
-	0x74, 0x79, 0x70, 0x65, 0x12, 0x2e, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f,
-	0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xfa, 0x42,
-	0x04, 0x22, 0x02, 0x28, 0x01, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x4c, 0x65,
-	0x6e, 0x67, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x69,
-	0x65, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x42,
-	0x07, 0xfa, 0x42, 0x04, 0x1a, 0x02, 0x28, 0x01, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50,
-	0x69, 0x65, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x05, 0x73, 0x74, 0x61,
-	0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10,
-	0x01, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0a, 0x70, 0x65, 0x65, 0x72,
-	0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x42, 0x07, 0xfa, 0x42,
-	0x04, 0x1a, 0x02, 0x28, 0x00, 0x52, 0x09, 0x70, 0x65, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-	0x12, 0x2a, 0x0a, 0x10, 0x68, 0x61, 0x73, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65,
-	0x50, 0x65, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x68, 0x61, 0x73, 0x41,
-	0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x65, 0x65, 0x72, 0x22, 0xf8, 0x01, 0x0a,
-	0x13, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x06,
-	0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x03, 0x63, 0x69,
-	0x64, 0x12, 0x32, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x55, 0x72, 0x6c, 0x4d, 0x65,
-	0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x07, 0x75, 0x72,
-	0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x68, 0x6f,
-	0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64,
-	0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x08, 0x70,
-	0x65, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x0c, 0x70, 0x69, 0x65, 0x63, 0x65,
-	0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e,
-	0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74,
-	0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0b, 0x70, 0x69, 0x65, 0x63,
-	0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x2a, 0x27, 0x0a, 0x07, 0x50, 0x61, 0x74, 0x74, 0x65,
-	0x72, 0x6e, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x32, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x43,
-	0x44, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x10, 0x02,
-	0x32, 0x9e, 0x03, 0x0a, 0x09, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x12, 0x49,
-	0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61,
-	0x73, 0x6b, 0x12, 0x1a, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50,
-	0x65, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19,
-	0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73,
-	0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x52, 0x65, 0x70,
-	0x6f, 0x72, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16,
-	0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65,
-	0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
-	0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x28, 0x01, 0x30,
-	0x01, 0x12, 0x41, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52,
-	0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65,
-	0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x16, 0x2e, 0x67,
+	0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x08, 0x75, 0x72, 0x6c, 0x5f, 0x6d, 0x65, 0x74,
+	0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x55,
+	0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01,
+	0x52, 0x07, 0x75, 0x72, 0x6c, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x30, 0x0a, 0x09, 0x70, 0x65, 0x65,
+	0x72, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x73,
+	0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x48, 0x6f, 0x73,
+	0x74, 0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x0c, 0x70,
+	0x69, 0x65, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x11, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x50, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61,
+	0x63, 0x6b, 0x65, 0x74, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x0b,
+	0x70, 0x69, 0x65, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x2a, 0x27, 0x0a, 0x07, 0x50,
+	0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x32, 0x50, 0x10, 0x00, 0x12,
+	0x07, 0x0a, 0x03, 0x43, 0x44, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x4f, 0x55, 0x52,
+	0x43, 0x45, 0x10, 0x02, 0x32, 0x9e, 0x03, 0x0a, 0x09, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
+	0x65, 0x72, 0x12, 0x49, 0x0a, 0x10, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x65,
+	0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1a, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c,
+	0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x19, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x52,
+	0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x46, 0x0a,
+	0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75,
+	0x6c, 0x74, 0x12, 0x16, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50,
+	0x69, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x15, 0x2e, 0x73, 0x63, 0x68,
+	0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x50, 0x61, 0x63, 0x6b, 0x65,
+	0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x50,
+	0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65,
+	0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
+	0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x09, 0x4c, 0x65, 0x61, 0x76,
+	0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65,
+	0x72, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x1a, 0x16, 0x2e, 0x67,
 	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
-	0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x09, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x54, 0x61, 0x73,
-	0x6b, 0x12, 0x15, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x50, 0x65,
-	0x65, 0x72, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
-	0x12, 0x37, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1a, 0x2e, 0x73,
-	0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73,
-	0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64,
-	0x75, 0x6c, 0x65, 0x72, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x46, 0x0a, 0x0c, 0x41, 0x6e, 0x6e,
-	0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1e, 0x2e, 0x73, 0x63, 0x68, 0x65,
-	0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54, 0x61,
-	0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
-	0x79, 0x42, 0x27, 0x5a, 0x25, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x64, 0x72, 0x61, 0x67,
-	0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x72, 0x70, 0x63,
-	0x2f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
+	0x6d, 0x70, 0x74, 0x79, 0x12, 0x37, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x54, 0x61, 0x73, 0x6b,
+	0x12, 0x1a, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x53, 0x74, 0x61,
+	0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x73,
+	0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x46, 0x0a,
+	0x0c, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x1e, 0x2e,
+	0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x75, 0x6e,
+	0x63, 0x65, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
+	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
+	0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x27, 0x5a, 0x25, 0x64, 0x37, 0x79, 0x2e, 0x69, 0x6f, 0x2f,
+	0x64, 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67,
+	0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -1447,26 +1473,27 @@ func file_pkg_rpc_scheduler_scheduler_proto_rawDescGZIP() []byte {
 var file_pkg_rpc_scheduler_scheduler_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
 var file_pkg_rpc_scheduler_scheduler_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
 var file_pkg_rpc_scheduler_scheduler_proto_goTypes = []interface{}{
-	(Pattern)(0),                // 0: scheduler.Pattern
-	(*PeerTaskRequest)(nil),     // 1: scheduler.PeerTaskRequest
-	(*RegisterResult)(nil),      // 2: scheduler.RegisterResult
-	(*SinglePiece)(nil),         // 3: scheduler.SinglePiece
-	(*PeerHost)(nil),            // 4: scheduler.PeerHost
-	(*PieceResult)(nil),         // 5: scheduler.PieceResult
-	(*PeerPacket)(nil),          // 6: scheduler.PeerPacket
-	(*PeerResult)(nil),          // 7: scheduler.PeerResult
-	(*PeerTarget)(nil),          // 8: scheduler.PeerTarget
-	(*StatTaskRequest)(nil),     // 9: scheduler.StatTaskRequest
-	(*Task)(nil),                // 10: scheduler.Task
-	(*AnnounceTaskRequest)(nil), // 11: scheduler.AnnounceTaskRequest
-	(*PeerPacket_DestPeer)(nil), // 12: scheduler.PeerPacket.DestPeer
-	(*base.UrlMeta)(nil),        // 13: base.UrlMeta
-	(*base.HostLoad)(nil),       // 14: base.HostLoad
-	(base.SizeScope)(0),         // 15: base.SizeScope
-	(*base.PieceInfo)(nil),      // 16: base.PieceInfo
-	(base.Code)(0),              // 17: base.Code
-	(*base.PiecePacket)(nil),    // 18: base.PiecePacket
-	(*emptypb.Empty)(nil),       // 19: google.protobuf.Empty
+	(Pattern)(0),                 // 0: scheduler.Pattern
+	(*PeerTaskRequest)(nil),      // 1: scheduler.PeerTaskRequest
+	(*RegisterResult)(nil),       // 2: scheduler.RegisterResult
+	(*SinglePiece)(nil),          // 3: scheduler.SinglePiece
+	(*PeerHost)(nil),             // 4: scheduler.PeerHost
+	(*PieceResult)(nil),          // 5: scheduler.PieceResult
+	(*PeerPacket)(nil),           // 6: scheduler.PeerPacket
+	(*PeerResult)(nil),           // 7: scheduler.PeerResult
+	(*PeerTarget)(nil),           // 8: scheduler.PeerTarget
+	(*StatTaskRequest)(nil),      // 9: scheduler.StatTaskRequest
+	(*Task)(nil),                 // 10: scheduler.Task
+	(*AnnounceTaskRequest)(nil),  // 11: scheduler.AnnounceTaskRequest
+	(*PeerPacket_DestPeer)(nil),  // 12: scheduler.PeerPacket.DestPeer
+	(*base.UrlMeta)(nil),         // 13: base.UrlMeta
+	(*base.HostLoad)(nil),        // 14: base.HostLoad
+	(base.SizeScope)(0),          // 15: base.SizeScope
+	(*base.ExtendAttribute)(nil), // 16: base.ExtendAttribute
+	(*base.PieceInfo)(nil),       // 17: base.PieceInfo
+	(base.Code)(0),               // 18: base.Code
+	(*base.PiecePacket)(nil),     // 19: base.PiecePacket
+	(*emptypb.Empty)(nil),        // 20: google.protobuf.Empty
 }
 var file_pkg_rpc_scheduler_scheduler_proto_depIdxs = []int32{
 	13, // 0: scheduler.PeerTaskRequest.url_meta:type_name -> base.UrlMeta
@@ -1475,34 +1502,36 @@ var file_pkg_rpc_scheduler_scheduler_proto_depIdxs = []int32{
 	0,  // 3: scheduler.PeerTaskRequest.pattern:type_name -> scheduler.Pattern
 	15, // 4: scheduler.RegisterResult.size_scope:type_name -> base.SizeScope
 	3,  // 5: scheduler.RegisterResult.single_piece:type_name -> scheduler.SinglePiece
-	16, // 6: scheduler.SinglePiece.piece_info:type_name -> base.PieceInfo
-	16, // 7: scheduler.PieceResult.piece_info:type_name -> base.PieceInfo
-	17, // 8: scheduler.PieceResult.code:type_name -> base.Code
-	14, // 9: scheduler.PieceResult.host_load:type_name -> base.HostLoad
-	12, // 10: scheduler.PeerPacket.main_peer:type_name -> scheduler.PeerPacket.DestPeer
-	12, // 11: scheduler.PeerPacket.steal_peers:type_name -> scheduler.PeerPacket.DestPeer
-	17, // 12: scheduler.PeerPacket.code:type_name -> base.Code
-	17, // 13: scheduler.PeerResult.code:type_name -> base.Code
-	13, // 14: scheduler.AnnounceTaskRequest.url_meta:type_name -> base.UrlMeta
-	4,  // 15: scheduler.AnnounceTaskRequest.peer_host:type_name -> scheduler.PeerHost
-	18, // 16: scheduler.AnnounceTaskRequest.piece_packet:type_name -> base.PiecePacket
-	1,  // 17: scheduler.Scheduler.RegisterPeerTask:input_type -> scheduler.PeerTaskRequest
-	5,  // 18: scheduler.Scheduler.ReportPieceResult:input_type -> scheduler.PieceResult
-	7,  // 19: scheduler.Scheduler.ReportPeerResult:input_type -> scheduler.PeerResult
-	8,  // 20: scheduler.Scheduler.LeaveTask:input_type -> scheduler.PeerTarget
-	9,  // 21: scheduler.Scheduler.StatTask:input_type -> scheduler.StatTaskRequest
-	11, // 22: scheduler.Scheduler.AnnounceTask:input_type -> scheduler.AnnounceTaskRequest
-	2,  // 23: scheduler.Scheduler.RegisterPeerTask:output_type -> scheduler.RegisterResult
-	6,  // 24: scheduler.Scheduler.ReportPieceResult:output_type -> scheduler.PeerPacket
-	19, // 25: scheduler.Scheduler.ReportPeerResult:output_type -> google.protobuf.Empty
-	19, // 26: scheduler.Scheduler.LeaveTask:output_type -> google.protobuf.Empty
-	10, // 27: scheduler.Scheduler.StatTask:output_type -> scheduler.Task
-	19, // 28: scheduler.Scheduler.AnnounceTask:output_type -> google.protobuf.Empty
-	23, // [23:29] is the sub-list for method output_type
-	17, // [17:23] is the sub-list for method input_type
-	17, // [17:17] is the sub-list for extension type_name
-	17, // [17:17] is the sub-list for extension extendee
-	0,  // [0:17] is the sub-list for field type_name
+	16, // 6: scheduler.RegisterResult.extend_attribute:type_name -> base.ExtendAttribute
+	17, // 7: scheduler.SinglePiece.piece_info:type_name -> base.PieceInfo
+	17, // 8: scheduler.PieceResult.piece_info:type_name -> base.PieceInfo
+	18, // 9: scheduler.PieceResult.code:type_name -> base.Code
+	14, // 10: scheduler.PieceResult.host_load:type_name -> base.HostLoad
+	16, // 11: scheduler.PieceResult.extend_attribute:type_name -> base.ExtendAttribute
+	12, // 12: scheduler.PeerPacket.main_peer:type_name -> scheduler.PeerPacket.DestPeer
+	12, // 13: scheduler.PeerPacket.steal_peers:type_name -> scheduler.PeerPacket.DestPeer
+	18, // 14: scheduler.PeerPacket.code:type_name -> base.Code
+	18, // 15: scheduler.PeerResult.code:type_name -> base.Code
+	13, // 16: scheduler.AnnounceTaskRequest.url_meta:type_name -> base.UrlMeta
+	4,  // 17: scheduler.AnnounceTaskRequest.peer_host:type_name -> scheduler.PeerHost
+	19, // 18: scheduler.AnnounceTaskRequest.piece_packet:type_name -> base.PiecePacket
+	1,  // 19: scheduler.Scheduler.RegisterPeerTask:input_type -> scheduler.PeerTaskRequest
+	5,  // 20: scheduler.Scheduler.ReportPieceResult:input_type -> scheduler.PieceResult
+	7,  // 21: scheduler.Scheduler.ReportPeerResult:input_type -> scheduler.PeerResult
+	8,  // 22: scheduler.Scheduler.LeaveTask:input_type -> scheduler.PeerTarget
+	9,  // 23: scheduler.Scheduler.StatTask:input_type -> scheduler.StatTaskRequest
+	11, // 24: scheduler.Scheduler.AnnounceTask:input_type -> scheduler.AnnounceTaskRequest
+	2,  // 25: scheduler.Scheduler.RegisterPeerTask:output_type -> scheduler.RegisterResult
+	6,  // 26: scheduler.Scheduler.ReportPieceResult:output_type -> scheduler.PeerPacket
+	20, // 27: scheduler.Scheduler.ReportPeerResult:output_type -> google.protobuf.Empty
+	20, // 28: scheduler.Scheduler.LeaveTask:output_type -> google.protobuf.Empty
+	10, // 29: scheduler.Scheduler.StatTask:output_type -> scheduler.Task
+	20, // 30: scheduler.Scheduler.AnnounceTask:output_type -> google.protobuf.Empty
+	25, // [25:31] is the sub-list for method output_type
+	19, // [19:25] is the sub-list for method input_type
+	19, // [19:19] is the sub-list for extension type_name
+	19, // [19:19] is the sub-list for extension extendee
+	0,  // [0:19] is the sub-list for field type_name
 }
 
 func init() { file_pkg_rpc_scheduler_scheduler_proto_init() }
diff --git a/pkg/rpc/scheduler/scheduler.pb.validate.go b/pkg/rpc/scheduler/scheduler.pb.validate.go
index 042197a5..6ae88dbf 100644
--- a/pkg/rpc/scheduler/scheduler.pb.validate.go
+++ b/pkg/rpc/scheduler/scheduler.pb.validate.go
@@ -191,6 +191,16 @@ func (m *RegisterResult) Validate() error {
 		}
 	}
 
+	if v, ok := interface{}(m.GetExtendAttribute()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return RegisterResultValidationError{
+				field:  "ExtendAttribute",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
 	switch m.DirectPiece.(type) {
 
 	case *RegisterResult_SinglePiece:
@@ -548,6 +558,16 @@ func (m *PieceResult) Validate() error {
 
 	// no validation rules for FinishedCount
 
+	if v, ok := interface{}(m.GetExtendAttribute()).(interface{ Validate() error }); ok {
+		if err := v.Validate(); err != nil {
+			return PieceResultValidationError{
+				field:  "ExtendAttribute",
+				reason: "embedded message failed validation",
+				cause:  err,
+			}
+		}
+	}
+
 	return nil
 }
 
diff --git a/pkg/rpc/scheduler/scheduler.proto b/pkg/rpc/scheduler/scheduler.proto
index b0f50643..9d36e2a7 100644
--- a/pkg/rpc/scheduler/scheduler.proto
+++ b/pkg/rpc/scheduler/scheduler.proto
@@ -62,6 +62,8 @@ message RegisterResult{
     // for tiny file
     bytes piece_content = 5;
   }
+  // task extend attribute, only direct_piece will carry extend attribute
+  base.ExtendAttribute extend_attribute = 6;
 }
 
 message SinglePiece{
@@ -115,6 +117,8 @@ message PieceResult{
   base.HostLoad host_load = 9;
   // currently completed piece count, -1 represent download failed
   int32 finished_count = 10;
+  // task extend attribute, only first success back source piece will carry extend attribute
+  base.ExtendAttribute extend_attribute = 11;
 }
 
 message PeerPacket{
diff --git a/pkg/source/clients/httpprotocol/header.go b/pkg/source/clients/httpprotocol/header.go
new file mode 100644
index 00000000..7c030950
--- /dev/null
+++ b/pkg/source/clients/httpprotocol/header.go
@@ -0,0 +1,34 @@
+/*
+ *     Copyright 2022 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package httpprotocol
+
+import "github.com/go-http-utils/headers"
+
+var PassThroughHeaders = map[string]struct{}{
+	// TODO implement cache control in dragonfly, then enable the following header pass through
+	// headers.CacheControl: {},
+	// headers.ETag:         {},
+	// headers.Expires:      {},
+	// headers.LastModified: {},
+
+	headers.ContentType:     {},
+	headers.ContentEncoding: {},
+}
+
+func AddPassThroughHeader(header string) {
+	PassThroughHeaders[header] = struct{}{}
+}
diff --git a/pkg/source/clients/httpprotocol/http_source_client.go b/pkg/source/clients/httpprotocol/http_source_client.go
index 6dfac626..370c79d2 100644
--- a/pkg/source/clients/httpprotocol/http_source_client.go
+++ b/pkg/source/clients/httpprotocol/http_source_client.go
@@ -182,20 +182,20 @@ func (client *httpSourceClient) Download(request *source.Request) (*source.Respo
 	if err != nil {
 		return nil, err
 	}
-	err = source.CheckResponseCode(resp.StatusCode, []int{http.StatusOK, http.StatusPartialContent})
-	if err != nil {
-		resp.Body.Close()
-		return nil, err
-	}
-	// FIXME check response "Content-Range" header, if not found, need to wrap resp.Body
 	response := source.NewResponse(
 		resp.Body,
+		source.WithStatus(resp.StatusCode, resp.Status),
+		source.WithValidate(func() error {
+			return source.CheckResponseCode(resp.StatusCode, []int{http.StatusOK, http.StatusPartialContent})
+		}),
+		source.WithHeader(exportPassThroughHeader(resp.Header)),
 		source.WithExpireInfo(
 			source.ExpireInfo{
 				LastModified: resp.Header.Get(headers.LastModified),
 				ETag:         resp.Header.Get(headers.ETag),
 			},
-		))
+		),
+	)
 	if resp.ContentLength > 0 {
 		response.ContentLength = resp.ContentLength
 	}
@@ -231,3 +231,14 @@ func (client *httpSourceClient) doRequest(method string, request *source.Request
 	}
 	return resp, nil
 }
+
+func exportPassThroughHeader(header http.Header) map[string]string {
+	var ph = map[string]string{}
+	for h := range PassThroughHeaders {
+		val := header.Get(h)
+		if len(val) > 0 {
+			ph[h] = val
+		}
+	}
+	return ph
+}
diff --git a/pkg/source/response.go b/pkg/source/response.go
index 4b4220d7..1e6734a5 100644
--- a/pkg/source/response.go
+++ b/pkg/source/response.go
@@ -28,6 +28,9 @@ type Response struct {
 	Header        Header
 	Body          io.ReadCloser
 	ContentLength int64
+	// validate this response is okay to transfer in p2p network, like status 200 or 206 in http is valid to do this,
+	// otherwise return status code to original client
+	Validate func() error
 }
 
 func NewResponse(rc io.ReadCloser, opts ...func(*Response)) *Response {
@@ -42,6 +45,9 @@ func NewResponse(rc io.ReadCloser, opts ...func(*Response)) *Response {
 		StatusCode:    http.StatusOK,
 		Body:          rc,
 		ContentLength: -1,
+		Validate: func() error {
+			return nil
+		},
 	}
 
 	for _, opt := range opts {
@@ -73,8 +79,18 @@ func WithHeader(header map[string]string) func(*Response) {
 
 func WithExpireInfo(info ExpireInfo) func(*Response) {
 	return func(resp *Response) {
-		resp.Header.Set(LastModified, info.LastModified)
-		resp.Header.Set(ETag, info.ETag)
+		if len(info.LastModified) > 0 {
+			resp.Header.Set(LastModified, info.LastModified)
+		}
+		if len(info.ETag) > 0 {
+			resp.Header.Set(ETag, info.ETag)
+		}
+	}
+}
+
+func WithValidate(validate func() error) func(*Response) {
+	return func(resp *Response) {
+		resp.Validate = validate
 	}
 }
 


Test patch:
diff --git a/cdn/supervisor/cdn/manager_test.go b/cdn/supervisor/cdn/manager_test.go
index 367c65d4..7c8e777c 100644
--- a/cdn/supervisor/cdn/manager_test.go
+++ b/cdn/supervisor/cdn/manager_test.go
@@ -162,6 +162,10 @@ func (suite *CDNManagerTestSuite) TestTriggerCDN() {
 				SourceRealDigest: "md5:f1e2488bba4d1267948d9e2f7008571c",
 				PieceMd5Sign:     "bb138842f338fff90af737e4a6b2c6f8e2a7031ca9d5900bc9b646f6406d890f",
 				Pieces:           new(sync.Map),
+				ExtendAttribute: &base.ExtendAttribute{Header: map[string]string{
+					"X-Dragonfly-Last-Modified": "Sun, 06 Jun 2021 12:52:30 GMT",
+					"X-Dragonfly-Etag":          "etag",
+				}},
 			},
 		},
 		{
@@ -195,6 +199,10 @@ func (suite *CDNManagerTestSuite) TestTriggerCDN() {
 				SourceRealDigest: "sha256:b9907b9a5ba2b0223868c201b9addfe2ec1da1b90325d57c34f192966b0a68c5",
 				PieceMd5Sign:     "bb138842f338fff90af737e4a6b2c6f8e2a7031ca9d5900bc9b646f6406d890f",
 				Pieces:           new(sync.Map),
+				ExtendAttribute: &base.ExtendAttribute{Header: map[string]string{
+					"X-Dragonfly-Last-Modified": "Sun, 06 Jun 2021 12:52:30 GMT",
+					"X-Dragonfly-Etag":          "etag",
+				}},
 			},
 		},
 	}
diff --git a/client/daemon/peer/peertask_manager_mock_test.go b/client/daemon/peer/peertask_manager_mock_test.go
index 7d255db6..5f59abfe 100644
--- a/client/daemon/peer/peertask_manager_mock_test.go
+++ b/client/daemon/peer/peertask_manager_mock_test.go
@@ -68,17 +68,18 @@ func (mr *MockTaskManagerMockRecorder) GetPieceManager() *gomock.Call {
 }
 
 // IsPeerTaskRunning mocks base method.
-func (m *MockTaskManager) IsPeerTaskRunning(id string) bool {
+func (m *MockTaskManager) IsPeerTaskRunning(taskID string) (Task, bool) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "IsPeerTaskRunning", id)
-	ret0, _ := ret[0].(bool)
-	return ret0
+	ret := m.ctrl.Call(m, "IsPeerTaskRunning", taskID)
+	ret0, _ := ret[0].(Task)
+	ret1, _ := ret[1].(bool)
+	return ret0, ret1
 }
 
 // IsPeerTaskRunning indicates an expected call of IsPeerTaskRunning.
-func (mr *MockTaskManagerMockRecorder) IsPeerTaskRunning(id interface{}) *gomock.Call {
+func (mr *MockTaskManagerMockRecorder) IsPeerTaskRunning(taskID interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPeerTaskRunning", reflect.TypeOf((*MockTaskManager)(nil).IsPeerTaskRunning), id)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPeerTaskRunning", reflect.TypeOf((*MockTaskManager)(nil).IsPeerTaskRunning), taskID)
 }
 
 // StartFileTask mocks base method.
diff --git a/client/daemon/peer/peertask_manager_test.go b/client/daemon/peer/peertask_manager_test.go
index 66b7bfc0..f375c784 100644
--- a/client/daemon/peer/peertask_manager_test.go
+++ b/client/daemon/peer/peertask_manager_test.go
@@ -400,7 +400,7 @@ func TestPeerTaskManager_TaskSuite(t *testing.T) {
 			mockHTTPSourceClient: nil,
 		},
 		{
-			name:                 "tidy size scope - p2p",
+			name:                 "tiny size scope - p2p",
 			taskData:             testBytes[:64],
 			pieceParallelCount:   4,
 			pieceSize:            1024,
diff --git a/client/daemon/peer/peertask_reuse_test.go b/client/daemon/peer/peertask_reuse_test.go
index 2003f9fc..81488db8 100644
--- a/client/daemon/peer/peertask_reuse_test.go
+++ b/client/daemon/peer/peertask_reuse_test.go
@@ -321,6 +321,15 @@ func TestReuseStreamPeerTask(t *testing.T) {
 						assert.Equal(taskID, req.TaskID)
 						return io.NopCloser(bytes.NewBuffer([]byte("1111111111"))), nil
 					})
+				sm.EXPECT().GetExtendAttribute(gomock.Any(),
+					gomock.Any()).AnyTimes().DoAndReturn(
+					func(ctx context.Context, req *storage.PeerTaskMetadata) (*base.ExtendAttribute, error) {
+						return &base.ExtendAttribute{
+							Header: map[string]string{
+								"Test": "test",
+							},
+						}, nil
+					})
 			},
 			verify: func(rc io.ReadCloser, attr map[string]string, ok bool) {
 				assert.True(ok)
@@ -399,6 +408,15 @@ func TestReuseStreamPeerTask(t *testing.T) {
 						assert.Equal(taskID, req.TaskID)
 						return io.NopCloser(bytes.NewBuffer([]byte("1111111111"))), nil
 					})
+				sm.EXPECT().GetExtendAttribute(gomock.Any(),
+					gomock.Any()).AnyTimes().DoAndReturn(
+					func(ctx context.Context, req *storage.PeerTaskMetadata) (*base.ExtendAttribute, error) {
+						return &base.ExtendAttribute{
+							Header: map[string]string{
+								"Test": "test",
+							},
+						}, nil
+					})
 			},
 			verify: func(rc io.ReadCloser, attr map[string]string, ok bool) {
 				assert.True(ok)
@@ -476,6 +494,15 @@ func TestReuseStreamPeerTask(t *testing.T) {
 						assert.Equal(taskID, req.TaskID)
 						return io.NopCloser(bytes.NewBuffer([]byte("1111111111"))), nil
 					})
+				sm.EXPECT().GetExtendAttribute(gomock.Any(),
+					gomock.Any()).AnyTimes().DoAndReturn(
+					func(ctx context.Context, req *storage.PeerTaskMetadata) (*base.ExtendAttribute, error) {
+						return &base.ExtendAttribute{
+							Header: map[string]string{
+								"Test": "test",
+							},
+						}, nil
+					})
 			},
 			verify: func(rc io.ReadCloser, attr map[string]string, ok bool) {
 				assert.True(ok)
diff --git a/client/daemon/rpcserver/rpcserver_test.go b/client/daemon/rpcserver/rpcserver_test.go
index 11d00e72..780f9ed8 100644
--- a/client/daemon/rpcserver/rpcserver_test.go
+++ b/client/daemon/rpcserver/rpcserver_test.go
@@ -32,6 +32,7 @@ import (
 
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/client/daemon/peer"
+	"d7y.io/dragonfly/v2/client/daemon/storage"
 	mock_peer "d7y.io/dragonfly/v2/client/daemon/test/mock/peer"
 	mock_storage "d7y.io/dragonfly/v2/client/daemon/test/mock/storage"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
@@ -421,6 +422,15 @@ func Test_SyncPieceTasks(t *testing.T) {
 							PieceMd5Sign:  "",
 						}, nil
 					})
+				mockStorageManger.EXPECT().GetExtendAttribute(gomock.Any(),
+					gomock.Any()).AnyTimes().DoAndReturn(
+					func(ctx context.Context, req *storage.PeerTaskMetadata) (*base.ExtendAttribute, error) {
+						return &base.ExtendAttribute{
+							Header: map[string]string{
+								"Test": "test",
+							},
+						}, nil
+					})
 				mockTaskManager := mock_peer.NewMockTaskManager(ctrl)
 				mockTaskManager.EXPECT().Subscribe(gomock.Any()).AnyTimes().DoAndReturn(
 					func(request *base.PieceTaskRequest) (*peer.SubscribeResponse, bool) {
diff --git a/client/daemon/rpcserver/seeder_test.go b/client/daemon/rpcserver/seeder_test.go
index 955970c4..e5148243 100644
--- a/client/daemon/rpcserver/seeder_test.go
+++ b/client/daemon/rpcserver/seeder_test.go
@@ -33,6 +33,7 @@ import (
 	"d7y.io/dragonfly/v2/client/clientutil"
 	"d7y.io/dragonfly/v2/client/config"
 	"d7y.io/dragonfly/v2/client/daemon/peer"
+	"d7y.io/dragonfly/v2/client/daemon/storage"
 	mock_peer "d7y.io/dragonfly/v2/client/daemon/test/mock/peer"
 	mock_storage "d7y.io/dragonfly/v2/client/daemon/test/mock/storage"
 	"d7y.io/dragonfly/v2/pkg/dfnet"
@@ -244,6 +245,15 @@ func Test_ObtainSeeds(t *testing.T) {
 							PieceMd5Sign:  "",
 						}, nil
 					})
+				mockStorageManger.EXPECT().GetExtendAttribute(gomock.Any(),
+					gomock.Any()).AnyTimes().DoAndReturn(
+					func(ctx context.Context, req *storage.PeerTaskMetadata) (*base.ExtendAttribute, error) {
+						return &base.ExtendAttribute{
+							Header: map[string]string{
+								"Test": "test",
+							},
+						}, nil
+					})
 				mockTaskManager := mock_peer.NewMockTaskManager(ctrl)
 				mockTaskManager.EXPECT().StartSeedTask(gomock.Any(), gomock.Any()).DoAndReturn(
 					func(ctx context.Context, req *peer.SeedTaskRequest) (*peer.SeedTaskResponse, error) {
diff --git a/client/daemon/test/mock/peer/peertask_manager.go b/client/daemon/test/mock/peer/peertask_manager.go
index f204bbe9..a12a9664 100644
--- a/client/daemon/test/mock/peer/peertask_manager.go
+++ b/client/daemon/test/mock/peer/peertask_manager.go
@@ -69,17 +69,18 @@ func (mr *MockTaskManagerMockRecorder) GetPieceManager() *gomock.Call {
 }
 
 // IsPeerTaskRunning mocks base method.
-func (m *MockTaskManager) IsPeerTaskRunning(id string) bool {
+func (m *MockTaskManager) IsPeerTaskRunning(taskID string) (peer.Task, bool) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "IsPeerTaskRunning", id)
-	ret0, _ := ret[0].(bool)
-	return ret0
+	ret := m.ctrl.Call(m, "IsPeerTaskRunning", taskID)
+	ret0, _ := ret[0].(peer.Task)
+	ret1, _ := ret[1].(bool)
+	return ret0, ret1
 }
 
 // IsPeerTaskRunning indicates an expected call of IsPeerTaskRunning.
-func (mr *MockTaskManagerMockRecorder) IsPeerTaskRunning(id interface{}) *gomock.Call {
+func (mr *MockTaskManagerMockRecorder) IsPeerTaskRunning(taskID interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPeerTaskRunning", reflect.TypeOf((*MockTaskManager)(nil).IsPeerTaskRunning), id)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsPeerTaskRunning", reflect.TypeOf((*MockTaskManager)(nil).IsPeerTaskRunning), taskID)
 }
 
 // StartFileTask mocks base method.
diff --git a/client/daemon/test/mock/storage/manager.go b/client/daemon/test/mock/storage/manager.go
index 6d666e4e..7cdfe8a9 100644
--- a/client/daemon/test/mock/storage/manager.go
+++ b/client/daemon/test/mock/storage/manager.go
@@ -39,6 +39,21 @@ func (m *MockTaskStorageDriver) EXPECT() *MockTaskStorageDriverMockRecorder {
 	return m.recorder
 }
 
+// GetExtendAttribute mocks base method.
+func (m *MockTaskStorageDriver) GetExtendAttribute(ctx context.Context, req *storage.PeerTaskMetadata) (*base.ExtendAttribute, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetExtendAttribute", ctx, req)
+	ret0, _ := ret[0].(*base.ExtendAttribute)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetExtendAttribute indicates an expected call of GetExtendAttribute.
+func (mr *MockTaskStorageDriverMockRecorder) GetExtendAttribute(ctx, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExtendAttribute", reflect.TypeOf((*MockTaskStorageDriver)(nil).GetExtendAttribute), ctx, req)
+}
+
 // GetPieces mocks base method.
 func (m *MockTaskStorageDriver) GetPieces(ctx context.Context, req *base.PieceTaskRequest) (*base.PiecePacket, error) {
 	m.ctrl.T.Helper()
@@ -326,6 +341,21 @@ func (mr *MockManagerMockRecorder) FindPartialCompletedTask(taskID, rg interface
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindPartialCompletedTask", reflect.TypeOf((*MockManager)(nil).FindPartialCompletedTask), taskID, rg)
 }
 
+// GetExtendAttribute mocks base method.
+func (m *MockManager) GetExtendAttribute(ctx context.Context, req *storage.PeerTaskMetadata) (*base.ExtendAttribute, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetExtendAttribute", ctx, req)
+	ret0, _ := ret[0].(*base.ExtendAttribute)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetExtendAttribute indicates an expected call of GetExtendAttribute.
+func (mr *MockManagerMockRecorder) GetExtendAttribute(ctx, req interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExtendAttribute", reflect.TypeOf((*MockManager)(nil).GetExtendAttribute), ctx, req)
+}
+
 // GetPieces mocks base method.
 func (m *MockManager) GetPieces(ctx context.Context, req *base.PieceTaskRequest) (*base.PiecePacket, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/source/clients/httpprotocol/http_source_client_test.go b/pkg/source/clients/httpprotocol/http_source_client_test.go
index 083da2ca..a64cb575 100644
--- a/pkg/source/clients/httpprotocol/http_source_client_test.go
+++ b/pkg/source/clients/httpprotocol/http_source_client_test.go
@@ -206,6 +206,10 @@ func (suite *HTTPSourceClientTestSuite) TestHttpSourceClientDownloadWithResponse
 				suite.True(tt.wantErr.Error() == err.Error())
 				return
 			}
+			if err = response.Validate(); err != nil {
+				suite.True(tt.wantErr.Error() == err.Error())
+				return
+			}
 			bytes, err := io.ReadAll(response.Body)
 			suite.Nil(err)
 			suite.Equal(tt.content, string(bytes))