code: func ParseJoinToken(token string) (*ParsedJoinToken, error) {
  	split := strings.Split(token, "-")
  	numParts := len(split)
  
  	// v1 has 4, v2 has 5
  	if numParts < 4 || split[0] != "SWMTKN" {
  		return nil, errInvalidJoinToken
  	}
  
  	var (
  		version int
  		fips    bool
  	)
  
  	switch split[1] {
  	case "1":
  		if numParts != 4 {
  			return nil, errInvalidJoinToken
  		}
  		version = 1
  	case "2":
  		if numParts != 5 || (split[2] != "0" && split[2] != "1") {
  			return nil, errInvalidJoinToken
  		}
  		version = 2
  		fips = split[2] == "1"
  	default:
  		return nil, errInvalidJoinToken
  	}
  
  	secret := split[numParts-1]
  	rootDigest := split[numParts-2]
  	if len(rootDigest) != base36DigestLen || len(secret) != maxGeneratedSecretLength {
  		return nil, errInvalidJoinToken
  	}
  
  	var digestInt big.Int
  	digestInt.SetString(rootDigest, joinTokenBase)
  
  	d, err := digest.Parse(fmt.Sprintf("sha256:%0[1]*s", 64, digestInt.Text(16)))
  	if err != nil {
  		return nil, err
  	}
  	return &ParsedJoinToken{
  		Version:    version,
  		RootDigest: d,
  		Secret:     secret,
  		FIPS:       fips,
  	}, nil
  }
docs: // ParseJoinToken parses a join token.  Current format is v2, but this is currently used only if the cluster requires
  // mandatory FIPS, in order to facilitate mixed version clusters.
  // v1: SWMTKN-1-<SHA256 digest of root CA cert in base 36, 0-left-padded to 50 chars>-<16-byte secret in base 36 0-left-padded to 25 chars>
  // v2: SWMTKN-2-<0/1 whether its FIPS or not>-<same rest of data as v1>