package downloader

import (
	
	
	
	
	
	
	

	

	
	
)

func ( *cdn) () {
	.hashesMux.Lock()
	.hashes = nil
	.hashOffsets = nil
	.hashesMux.Unlock()
}

func ( *cdn) () {
	.windowsMux.Lock()
	// Drop all previously verified window payloads whenever redirect/token scope
	// changes to avoid mixing data that belongs to different CDN contexts.
	.windows = nil
	.windowsFIFO = nil
	.windowsMux.Unlock()
}

func ( *cdn) ( tg.FileHash) ([]byte, bool) {
	.windowsMux.Lock()
	defer .windowsMux.Unlock()

	,  := .windows[.Offset]
	if ! {
		return nil, false
	}
	if len() == 0 || len() > .Limit {
		return nil, false
	}

	// Returned slice is treated as read-only by callers.
	return , true
}

func ( *cdn) ( tg.FileHash,  []byte) {
	if .Limit <= 0 || len() == 0 || len() > .Limit {
		return
	}

	.windowsMux.Lock()
	defer .windowsMux.Unlock()
	if .windows == nil {
		.windows = make(map[int64][]byte)
	}
	if ,  := .windows[.Offset]; ! {
		.windowsFIFO = append(.windowsFIFO, .Offset)
	}
	// Store a copy to keep cache immutable relative to caller buffers.
	.windows[.Offset] = append([]byte(nil), ...)
	for len(.windowsFIFO) > maxVerifiedWindowCache {
		 := .windowsFIFO[0]
		.windowsFIFO = .windowsFIFO[1:]
		// FIFO eviction is enough here: access pattern is near-sequential and we
		// only need to cap memory, not optimize for perfect hit rate.
		delete(.windows, )
	}
}

func ( *cdn) ( []tg.FileHash) {
	if len() == 0 {
		return
	}

	.hashesMux.Lock()
	if .hashes == nil {
		.hashes = make(map[int64]tg.FileHash, len())
	}
	for ,  := range  {
		if .Limit <= 0 {
			continue
		}

		// Keep sorted unique offsets index for O(log n) range lookup.
		if ,  := .hashes[.Offset]; ! {
			 := sort.Search(len(.hashOffsets), func( int) bool {
				return .hashOffsets[] >= .Offset
			})
			if  == len(.hashOffsets) {
				.hashOffsets = append(.hashOffsets, .Offset)
			} else if .hashOffsets[] != .Offset {
				.hashOffsets = append(.hashOffsets, 0)
				copy(.hashOffsets[+1:], .hashOffsets[:])
				.hashOffsets[] = .Offset
			}
		}
		.hashes[.Offset] = 
	}
	.hashesMux.Unlock()
}

func ( *cdn) ( int64) (tg.FileHash, bool) {
	.hashesMux.RLock()
	,  := .hashes[]
	if  {
		.hashesMux.RUnlock()
		return , true
	}

	// Fast-path map lookup works only for exact hash offsets. For unaligned
	// part sizes resolve containing window by predecessor offset in sorted index.
	if len(.hashOffsets) == 0 {
		.hashesMux.RUnlock()
		return tg.FileHash{}, false
	}

	 := sort.Search(len(.hashOffsets), func( int) bool {
		return .hashOffsets[] > 
	}) - 1
	if  < 0 {
		.hashesMux.RUnlock()
		return tg.FileHash{}, false
	}

	,  := .hashes[.hashOffsets[]]
	if ! || .Limit <= 0 {
		.hashesMux.RUnlock()
		return tg.FileHash{}, false
	}
	 := .Offset + int64(.Limit)
	 = 
	 =  >= .Offset &&  < 
	.hashesMux.RUnlock()
	return , 
}

func ( *cdn) ( context.Context,  int64) (tg.FileHash, error) {
	if ,  := .hash();  {
		return , nil
	}

	// Ask server for the current offset window and cache returned range.
	for  := 0;  < maxRetryAttempts; ++ {
		if  := .Err();  != nil {
			return tg.FileHash{}, 
		}

		,  := .Hashes(, )
		if  != nil {
			return tg.FileHash{}, errors.Wrapf(, "load CDN hashes at offset=%d", )
		}
		// Cache batch and retry lookup: server may return a range of windows
		// where requested offset is not the first element.
		.cacheHashes()
		if ,  := .hash();  {
			return , nil
		}
	}

	return tg.FileHash{}, retryLimitErr(
		"cdn hash lookup",
		maxRetryAttempts,
		errors.Errorf("hash for offset %d not found", ),
	)
}

func ( tg.FileHash) string {
	 := make([]byte, 0, len(.Hash)+64)
	 = strconv.AppendInt(, .Offset, 10)
	 = append(, ':')
	 = strconv.AppendInt(, int64(.Limit), 10)
	 = append(, ':')
	 = append(, .Hash...)
	return string()
}

func ( *cdn) ( context.Context,  tg.FileHash) ([]byte, error) {
	if ,  := .cachedWindow();  {
		return , nil
	}

	 := windowLoadKey()
	, ,  := .windowsLoad.Do(, func() (interface{}, error) {
		if ,  := .cachedWindow();  {
			return , nil
		}

		// Fetching a whole window can return a shorter payload only for the
		// last file segment, so we accept len(full.data) <= hash.Limit.
		,  := .Chunk(, .Offset, .Limit)
		if  != nil {
			return nil, errors.Wrapf(, "load full hash window at offset=%d limit=%d", .Offset, .Limit)
		}
		if len(.data) == 0 || len(.data) > .Limit {
			return nil, errors.Errorf(
				"invalid CDN window length at offset=%d max=%d got=%d",
				.Offset, .Limit, len(.data),
			)
		}
		if !bytes.Equal(crypto.SHA256(.data), .Hash) {
			return nil, errors.Wrapf(
				ErrHashMismatch,
				"at offset=%d size=%d",
				.Offset, .Limit,
			)
		}

		.cacheWindow(, .data)
		return .data, nil
	})
	if  != nil {
		return nil, 
	}

	,  := .([]byte)
	if ! {
		return nil, errors.Errorf("unexpected window type %T", )
	}
	return , nil
}

func ( *cdn) ( context.Context,  int64,  int,  []byte) error {
	if !.verify || len() == 0 {
		return nil
	}
	 :=  > 0 && len() < 

	// Inline mode validates every hash window covered by this chunk.
	// For windows split by custom part sizes, we load and verify the full window
	// (cached) and then patch overlapping bytes in this chunk with verified data.
	 := 
	 :=  + int64(len())
	for  := ;  < ; {
		,  := .hashForOffset(, )
		if  != nil {
			return 
		}
		if .Limit <= 0 {
			return errors.Errorf("invalid CDN hash limit %d at offset %d", .Limit, )
		}
		 := .Offset
		 := .Offset + int64(.Limit)
		if  <=  {
			return errors.Errorf("invalid CDN hash window [%d,%d) at offset %d", , , )
		}

		switch {
		case  >=  &&  <= :
			// Full hash window is present in current chunk: verify directly.
			 := int( - )
			 := int( - )
			if !bytes.Equal(crypto.SHA256([:]), .Hash) {
				return errors.Wrapf(
					ErrHashMismatch,
					"at offset=%d size=%d",
					, .Limit,
				)
			}

		case  &&  >=  &&  <  &&  > :
			// Final short chunk: Telegram keeps nominal hash limit, but hash is
			// computed on actual remaining tail bytes.
			 := int( - )
			if !bytes.Equal(crypto.SHA256([:]), .Hash) {
				return ErrHashMismatch
			}
			return nil

		default:
			// Hash window crosses current chunk boundaries.
			//
			// TDesktop-style behavior: validate complete hash window and then apply
			// verified overlap to current chunk. This preserves integrity checks for
			// custom part sizes without forcing eager verifier mode globally.
			,  := .loadAndVerifyWindow(, )
			if  != nil {
				return 
			}

			 := 
			if  >  {
				 = 
			}
			 :=  + int64(len())
			 := 
			if  <  {
				 = 
			}
			if  <=  {
				return errors.Errorf(
					"invalid overlap for hash window [%d,%d) and chunk [%d,%d)",
					, , , ,
				)
			}
			 := int( - )
			 := int( - )
			 := int( - )
			 := int( - )
			// Replace bytes in-place with verified overlap so the caller receives a
			// fully verified chunk even when hash windows are split by part size.
			copy([:], [:])
		}
		 = 
	}
	return nil
}

// decrypt decrypts file chunk from Telegram CDN.
// See https://core.telegram.org/cdn#decrypting-files.
func ( *cdn) ( []byte,  int64,  *tg.UploadFileCDNRedirect) ([]byte, error) {
	,  := aes.NewCipher(.EncryptionKey)
	if  != nil {
		return nil, errors.Wrap(, "create cipher")
	}
	if .BlockSize() != len(.EncryptionIv) {
		return nil, errors.Errorf(
			"invalid IV or key length, block size %d != IV %d",
			.BlockSize(), len(.EncryptionIv),
		)
	}

	 := .pool.GetSize(len(.EncryptionIv))
	defer .pool.Put()
	copy(.Buf, .EncryptionIv)

	binary.BigEndian.PutUint32(.Buf[.Len()-4:], uint32(/16))

	 := make([]byte, len())
	cipher.NewCTR(, .Buf).XORKeyStream(, )
	return , nil
}