anchor.diff:          stream.WriteLine(secretKeys)
           stream.Close()
   
  -    let readSecretKeysFromDisk (file : string option) = 
  +    let readCryptoKeysFromDisk (file : string option) = 
           try 
               match file with 
               | Some file -> file |> File.ReadAllText
               | _ -> Environment.GetEnvironmentVariable("HOME") + @"\.ssh\ff-secretKeys.json" |> File.ReadAllText
  -        with | _ -> "" 
  +        with | _ -> ""
  +
  +
  +    let decryptCryptoKeys secrets cryptoKeys = 
  +        let ck_pl = (cryptoKeys?payload).AsString() |> JsonValue.Parse
  +
  +        // https://docs.services.mozilla.com/sync/storageformat5.html
  +        //
  +        //    ciphertext  = record.ciphertext
  +        //    iv          = record.iv
  +        //    record_hmac = record.hmac
  +        let record = { iv         = (ck_pl?IV).AsString()
  +                       ciphertext = (ck_pl?ciphertext).AsString()
  +                       hmac       = (ck_pl?hmac).AsString() }
  +        //
  +        //    encryption_key = bundle.encryption_key
  +        //    hmac_key       = bundle.hmac_key
  +        let bundle = syncKeyBundle secrets.username (secrets.encryptionpassphrase |> base32Decode)
  +        //
  +        //    local_hmac = HMACSHA256(hmac_key, base64(ciphertext))
  +        let local_hmac = record.ciphertext |> Convert.FromBase64String |> (new HMACSHA256(bundle.hmac_key)).ComputeHash
  +        //
  +        //    if local_hmac != record_hmac:
  +        //      throw Error("HMAC verification failed.")
  +        //
  +        //    cleartext = AESDecrypt(ciphertext, encryption_key, iv)
  +        let cleartext = DecryptAES record.ciphertext bundle.encryption_key (record.iv |> Convert.FromBase64String)
  +        cleartext
  +
  +
  +    let getCryptoKeys secrets =
  +        try
  +            fetchCryptoKeys secrets.username secrets.password
  +            |> JsonValue.Parse
  +            |> decryptCryptoKeys secrets
  +            |> keepAsciiPrintableChars
  +            |> JsonValue.Parse 
  +            |> fun x -> (x.GetProperty "default").AsArray()
  +            |> Array.map (fun x -> x.AsString())
  +            |> Array.map Convert.FromBase64String
  +            |> Some
  +        with 
  +        | _ -> None
  +
  +module GeneralInfo = 
  +    open Secrets
  +    open ServerAPI
  +    
  +    let s = Secrets.secrets
  +    let url = clusterURL s.username  
  +
  +    let fetchInfoCollections username password =
  +        let url = (clusterURL username) + "1.1/" + username + "/info/collections"
  +        fetchUrlResponse url "GET" (Some (username, password)) None None None
  +
  +    let fetchInfoQuota username password =
  +        let url = (clusterURL username) + "1.1/" + username + "/info/quota"
  +        fetchUrlResponse url "GET" (Some (username, password)) None None None
  +
  +    let fetchInfoCollectionUsage username password =
  +        let url = (clusterURL username) + "1.1/" + username + "/info/collection_usage"
  +        fetchUrlResponse url "GET" (Some (username, password)) None None None
  +
  +    let fetchInfoCollectionCounts username password =
  +        let url = (clusterURL username) + "1.1/" + username + "/info/collection_counts"
  +        fetchUrlResponse url "GET" (Some (username, password)) None None None
  +
  +    let deleteStorage username password =
  +        let url = (clusterURL username) + "1.1/" + username
  +        fetchUrlResponse url "GET" (Some (username, password)) None None None
  +
  +    let ic = fetchInfoCollections s.username s.password
  +
  +    let iq = fetchInfoQuota s.username s.password
  +
  +    let icu = fetchInfoCollectionUsage s.username s.password
  +
  +    let icc = fetchInfoCollectionCounts s.username s.password
  +
  +
  +module Collections =
  +    open Secrets
  +    open ServerAPI
  +    open CryptoKeys
  +
  +    let s = Secrets.secrets
  +    let url = clusterURL s.username
  +    
  +    // https://github.com/mikerowehl/firefox-sync-client-php/blob/master/sync.php#L94
  +    let fetchCollection username password collection =
  +        let url = (clusterURL username) + "1.1/" + username + "/storage/" + collection + "?full=1"
  +        fetchUrlResponse url "GET" (Some (username, password)) None None None
  +
  +    let bm' = fetchCollection s.username s.password "bookmarks"
  +
  +    let parseCollection collection = 
  +        collection
  +        |> JsonValue.Parse 
  +        |> fun x -> x.AsArray() 
  +        |> Array.map string 
  +        |> Array.map (fun x -> JsonValue.Parse x) 
  +        |> Array.map (fun x -> (x?payload).AsString())
  +        |> Array.map (fun x -> JsonValue.Parse x)
  +        |> Array.map (fun x -> { iv = x?IV.AsString(); ciphertext = x?ciphertext.AsString(); hmac = x?hmac.AsString() } )
  +
  +    let bm = parseCollection bm'
  +    let cryptoKeysCleartext = 
  +        readCryptoKeysFromDisk None 
  +        |> JsonValue.Parse 
  +        |> decryptCryptoKeys secrets
  +    let key = 
  +        cryptoKeysCleartext 
  +        |> keepAsciiPrintableChars
  +        |> JsonValue.Parse 
  +        |> fun x -> (x.GetProperty "default").AsArray()
  +        |> fun x -> x.[0].AsString()
  +        |> Convert.FromBase64String
  +    let cleartext = [|for b in bm do yield DecryptAES b.ciphertext key (b.iv |> Convert.FromBase64String) |]
  +
  +
  +    let cleartext' = 
  +        cleartext
  +        |> Array.map keepAsciiPrintableChars 
  +        |> Array.map JsonValue.Parse
  +
  +    let x = 
  +        cleartext'.[0] 
  +        |> fun x -> x.TryGetProperty "keyword"
  +        |> fun x -> match x with | Some x -> x.AsString() | _ -> ""
  +
  +    let x' = 
  +        cleartext'.[0] 
  +        |> fun x -> x.TryGetProperty "parentName"
  +        |> fun x -> match x with | Some x -> x.AsString() | _ -> ""
   
   
   module Test = 
       open Utilities
  +    open CryptoKeys
   
       // base32Decode
   
  
positive.code:     let serverURL username = "https://auth.services.mozilla.com/user/1.0/" + username + "/node/weave"
      let clusterURL username = fetchUrlResponse (username |> serverURL) "GET" None None None None
  
      let fetchCryptoKeys username password =
          let url = (clusterURL username) + "1.1/" + username + "/storage/crypto/keys"
          fetchUrlResponse url "GET" (Some (username, password)) None None None
  
negative.code:         failwith "syncKeyBundle failed"
  
      
      // writeCryptoKeysToDisk
  
      try
          writeCryptoKeysToDisk secrets.username secrets.password None
      with | _ -> failwith "writeCryptoKeysToDisk failed"
  
  
      // getRecordFields/getRecordField
  
      let x = {value = "value"; name = Some "name"; ``type`` = Some "type"; }
  
      let x' = getRecordFields x
  
      let x'' = { value = getRecordField x x'.[0]; name = getRecordField x x'.[1]; ``type`` = getRecordField x x'.[2] }
  
      if x <> x'' then 
          failwith "getRecordFields/getRecordField failed"