// unpackRemoteServerListFile reads a file that contains a
// zlib compressed authenticated data package, validates
// the package, and returns the payload.
func unpackRemoteServerListFile(
	config *Config, filename string) (string, error) {

	fileReader, err := os.Open(filename)
	if err != nil {
		return "", common.ContextError(err)
	}
	defer fileReader.Close()

	zlibReader, err := zlib.NewReader(fileReader)
	if err != nil {
		return "", common.ContextError(err)
	}

	dataPackage, err := ioutil.ReadAll(zlibReader)
	zlibReader.Close()
	if err != nil {
		return "", common.ContextError(err)
	}

	payload, err := common.ReadAuthenticatedDataPackage(
		dataPackage, config.RemoteServerListSignaturePublicKey)
	if err != nil {
		return "", common.ContextError(err)
	}

	return payload, nil
}
// UnpackRegistry decompresses, validates, and loads a
// JSON encoded OSL registry.
func UnpackRegistry(
	compressedRegistry []byte, signingPublicKey string) (*Registry, []byte, error) {

	packagedRegistry, err := uncompress(compressedRegistry)
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	encodedRegistry, err := common.ReadAuthenticatedDataPackage(
		packagedRegistry, signingPublicKey)
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	registryJSON, err := base64.StdEncoding.DecodeString(encodedRegistry)
	if err != nil {
		return nil, nil, common.ContextError(err)
	}

	registry, err := LoadRegistry(registryJSON)
	return registry, registryJSON, err
}
// UnpackOSL reassembles the key for the OSL specified by oslID and uses
// that key to decrypt oslFileContents, uncompress the contents, validate
// the authenticated package, and extract the payload.
// Clients will call UnpackOSL for OSLs indicated by GetSeededOSLIDs along
// with their downloaded content.
// SLOKLookup is called to determine which SLOKs are seeded with the client.
func (registry *Registry) UnpackOSL(
	lookup SLOKLookup,
	oslID []byte,
	oslFileContents []byte,
	signingPublicKey string) (string, error) {

	fileSpec, ok := registry.oslIDLookup[string(oslID)]
	if !ok {
		return "", common.ContextError(errors.New("unknown OSL ID"))
	}

	ok, fileKey, err := fileSpec.KeyShares.reassembleKey(lookup, true)
	if err != nil {
		return "", common.ContextError(err)
	}
	if !ok {
		return "", common.ContextError(errors.New("unseeded OSL"))
	}

	decryptedContents, err := unbox(fileKey, oslFileContents)
	if err != nil {
		return "", common.ContextError(err)
	}

	dataPackage, err := uncompress(decryptedContents)
	if err != nil {
		return "", common.ContextError(err)
	}

	oslPayload, err := common.ReadAuthenticatedDataPackage(
		dataPackage, signingPublicKey)
	if err != nil {
		return "", common.ContextError(err)
	}

	return oslPayload, nil
}
// getRoutes makes a web request to download fresh routes data for the
// given region, as indicated by the tunnel. It uses web caching, If-None-Match/ETag,
// to save downloading known routes data repeatedly. If the web request
// fails and cached routes data is present, that cached data is returned.
func (classifier *SplitTunnelClassifier) getRoutes(tunnel *Tunnel) (routesData []byte, err error) {

	url := fmt.Sprintf(classifier.fetchRoutesUrlFormat, tunnel.serverContext.clientRegion)
	request, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, common.ContextError(err)
	}

	etag, err := GetSplitTunnelRoutesETag(tunnel.serverContext.clientRegion)
	if err != nil {
		return nil, common.ContextError(err)
	}
	if etag != "" {
		request.Header.Add("If-None-Match", etag)
	}

	tunneledDialer := func(_, addr string) (conn net.Conn, err error) {
		return tunnel.sshClient.Dial("tcp", addr)
	}
	transport := &http.Transport{
		Dial: tunneledDialer,
		ResponseHeaderTimeout: time.Duration(*tunnel.config.FetchRoutesTimeoutSeconds) * time.Second,
	}
	httpClient := &http.Client{
		Transport: transport,
		Timeout:   time.Duration(*tunnel.config.FetchRoutesTimeoutSeconds) * time.Second,
	}

	// At this time, the largest uncompressed routes data set is ~1MB. For now,
	// the processing pipeline is done all in-memory.

	useCachedRoutes := false

	response, err := httpClient.Do(request)

	if err == nil &&
		(response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotModified) {
		response.Body.Close()
		err = fmt.Errorf("unexpected response status code: %d", response.StatusCode)
	}
	if err != nil {
		NoticeAlert("failed to request split tunnel routes package: %s", common.ContextError(err))
		useCachedRoutes = true
	}

	if !useCachedRoutes {
		defer response.Body.Close()
		if response.StatusCode == http.StatusNotModified {
			useCachedRoutes = true
		}
	}

	var routesDataPackage []byte
	if !useCachedRoutes {
		routesDataPackage, err = ioutil.ReadAll(response.Body)
		if err != nil {
			NoticeAlert("failed to download split tunnel routes package: %s", common.ContextError(err))
			useCachedRoutes = true
		}
	}

	var encodedRoutesData string
	if !useCachedRoutes {
		encodedRoutesData, err = common.ReadAuthenticatedDataPackage(
			routesDataPackage, classifier.routesSignaturePublicKey)
		if err != nil {
			NoticeAlert("failed to read split tunnel routes package: %s", common.ContextError(err))
			useCachedRoutes = true
		}
	}

	var compressedRoutesData []byte
	if !useCachedRoutes {
		compressedRoutesData, err = base64.StdEncoding.DecodeString(encodedRoutesData)
		if err != nil {
			NoticeAlert("failed to decode split tunnel routes: %s", common.ContextError(err))
			useCachedRoutes = true
		}
	}

	if !useCachedRoutes {
		zlibReader, err := zlib.NewReader(bytes.NewReader(compressedRoutesData))
		if err == nil {
			routesData, err = ioutil.ReadAll(zlibReader)
			zlibReader.Close()
		}
		if err != nil {
			NoticeAlert("failed to decompress split tunnel routes: %s", common.ContextError(err))
			useCachedRoutes = true
		}
	}

	if !useCachedRoutes {
		etag := response.Header.Get("ETag")
		if etag != "" {
			err := SetSplitTunnelRoutes(tunnel.serverContext.clientRegion, etag, routesData)
			if err != nil {
				NoticeAlert("failed to cache split tunnel routes: %s", common.ContextError(err))
				// Proceed with fetched data, even when we can't cache it
			}
		}
	}

	if useCachedRoutes {
		routesData, err = GetSplitTunnelRoutesData(tunnel.serverContext.clientRegion)
		if err != nil {
			return nil, common.ContextError(err)
		}
		if routesData == nil {
			return nil, common.ContextError(errors.New("no cached routes"))
		}
	}

	return routesData, nil
}