// cmenctool -- dealing with KEKS-encoded cm/encrypted utility
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"bytes"
	"crypto/cipher"
	"crypto/ecdh"
	"crypto/hkdf"
	"crypto/rand"
	"crypto/sha3"
	"errors"
	"flag"
	"hash"
	"io"
	"log"
	"os"
	"strconv"

	"github.com/google/uuid"
	"go.cypherpunks.su/balloon/v3"
	"golang.org/x/crypto/blake2b"
	"golang.org/x/crypto/chacha20poly1305"

	"go.cypherpunks.su/keks"
	"go.cypherpunks.su/keks/cm"
	cmenc "go.cypherpunks.su/keks/cm/enc"
	cmballoon "go.cypherpunks.su/keks/cm/enc/balloon"
	ballooncost "go.cypherpunks.su/keks/cm/enc/balloon/cost"
	chapoly "go.cypherpunks.su/keks/cm/enc/chapoly"
	mceliece6960119x25519 "go.cypherpunks.su/keks/cm/enc/mceliece6960119-x25519"
	mceliece6960119 "go.cypherpunks.su/keks/cm/enc/mceliece6960119-x25519/mceliece6960119"
	sntrup761x25519 "go.cypherpunks.su/keks/cm/enc/sntrup761-x25519"
	sntrup761kem "go.cypherpunks.su/keks/cm/enc/sntrup761-x25519/sntrup761/kem"
	sntrup761 "go.cypherpunks.su/keks/cm/enc/sntrup761-x25519/sntrup761/kem/ntruprime/sntrup761"
	cmhash "go.cypherpunks.su/keks/cm/hash"
	"go.cypherpunks.su/keks/cm/sign"
	"go.cypherpunks.su/keks/schema"
	"go.cypherpunks.su/keks/types"
)

const (
	FdPubR = 4
	FdPrvR = 8

	X25519KeyLen = 32
)

func blake2bHash() hash.Hash {
	h, err := blake2b.New512(nil)
	if err != nil {
		panic(err)
	}
	return h
}

func parsePrv(data []byte) (av cm.AV, tail []byte, err error) {
	data, err = cmballoon.PossibleInteractiveDecrypt(data)
	if err != nil {
		return
	}
	{
		var magic keks.Magic
		magic, data = keks.StripMagic(data)
		if magic != sign.PrvMagic {
			err = errors.New("wrong magic")
			return
		}
	}
	sign.PrvParse(data)
	d := keks.NewDecoderFromBytes(data, &keks.DecodeOpts{MaxStrLen: 1 << 16})
	{
		var v any
		if v, err = d.Decode(); err != nil {
			return
		}
		if err = schema.Check("av", sign.PubSchemas, v); err != nil {
			return
		}
	}
	if err = d.UnmarshalStruct(&av); err != nil {
		return
	}
	tail = d.B
	return
}

func main() {
	log.SetFlags(log.Lshortfile)
	flag.Usage = usage
	setId := flag.String("id", "", "Set that /id instead of autogeneration")
	includeTo := flag.Bool("include-to", false, `Include "to" field in KEMs`)
	passphrase := flag.Bool("p", false, "Use passphrase")
	balloonS := flag.Int("balloon-s", 1<<17, "Balloon's space cost")
	balloonT := flag.Int("balloon-t", 4, "Balloon's time cost")
	balloonP := flag.Int("balloon-p", 2, "Balloon's number of threads")
	doDecrypt := flag.Bool("d", false, "Decrypt")
	parallel := flag.Int("parallel", cmhash.DefaultNumCPU, "Parallel cryptors")
	noblob := flag.Bool("embed", false, "Include payload into container")
	flag.Parse()

	fdPubR := os.NewFile(FdPubR, "pub-in")
	var pubs []cm.AV
	var pubIds [][]byte
	if data, err := io.ReadAll(fdPubR); err == nil {
		for len(data) > 0 {
			var signed *sign.Signed
			signed, data, err = sign.PubParse(data)
			if err != nil {
				log.Fatalln("public key:", len(pubs), ":", err)
			}
			pubData := signed.PubData()
			if !pubData.Can(sign.KUKEM) {
				log.Println(
					"public key:", len(pubs), ": does not have",
					sign.KUKEM, "key usage",
				)
			}
			if len(pubData.Pub) != 1 {
				log.Fatalln("public key:", len(pubs), ": expected single public key")
			}
			pubs = append(pubs, pubData.Pub[0])
			pubIds = append(pubIds, pubData.Id)
		}
	}
	fdPubR.Close()

	fdPrvR := os.NewFile(FdPrvR, "prv-in")
	var prvs []*cm.AV
	if data, err := io.ReadAll(fdPrvR); err == nil {
		for len(data) > 0 {
			var av cm.AV
			av, data, err = parsePrv(data)
			if err != nil {
				log.Fatalln("private key:", len(prvs), ":", err)
			}
			prvs = append(prvs, &av)
		}
	}
	fdPrvR.Close()

	var err error
	var cek []byte
	if *doDecrypt {
		{
			d := keks.NewDecoderFromReader(os.Stdin, &keks.MagicDecodeOpts)
			var t types.Type
			t, err = d.DecodeAtom()
			if err != nil {
				log.Fatal(err)
			}
			if t != types.Magic {
				log.Fatal("no magic met")
			}
			if d.Iter().Magic() != cmenc.Magic {
				log.Fatal("wrong magic")
			}
		}
		var encrypted cmenc.Encrypted
		{
			d := keks.NewDecoderFromReader(os.Stdin, nil)
			var v any
			v, err = d.Decode()
			if err != nil {
				log.Fatal(err)
			}
			if err = schema.Check("encrypted", cmenc.EncryptedSchemas, v); err != nil {
				log.Fatal(err)
			}
			for _, kem := range v.(map[string]any)["kem"].([]any) {
				kemMap := kem.(map[string]any)
				switch kemMap["a"] {
				case cmballoon.BalloonBLAKE2bHKDF:
					err = schema.Check("kem-balloon-blake2b-hkdf", cmenc.EncryptedSchemas, kem)
				case sntrup761x25519.SNTRUP761X25519HKDFBLAKE2b:
					fallthrough
				case mceliece6960119x25519.ClassicMcEliece6960119X25519HKDFSHAKE256:
					err = schema.Check("kem-with-encap", cmenc.EncryptedSchemas, kem)
				default:
					log.Fatal("unsupported KEM algorithm")
				}
			}
			if err != nil {
				log.Fatal(err)
			}
			if err = d.UnmarshalStruct(&encrypted); err != nil {
				log.Fatal(err)
			}
		}
		if encrypted.DEM.A != chapoly.DEMAlgo {
			log.Fatalln("unsupported DEM:", encrypted.DEM.A)
		}
		if len(encrypted.KEM) == 0 {
			log.Fatalln("no KEMs")
		}
		for kemIdx, kem := range encrypted.KEM {
			switch kem.A {
			case cmballoon.BalloonBLAKE2bHKDF:
				if !*passphrase {
					log.Println(kemIdx, kem.A, "skipping because no -passwd")
					continue
				}
				var passwd []byte
				passwd, err = cmenc.ReadPasswd("Passphrase for " + strconv.Itoa(kemIdx) + " KEM:")
				if err != nil {
					log.Fatal(err)
				}
				cek, err = cmballoon.Decapsulate(kem, encrypted.Id[:], passwd)
				if err != nil {
					log.Print(err)
					continue
				}
				if len(cek) != chapoly.CEKLen {
					log.Println(kemIdx, kem.A, "wrong key len, skipping")
					continue
				}
			case sntrup761x25519.SNTRUP761X25519HKDFBLAKE2b:
				if len(prvs) == 0 {
					log.Println(kemIdx, kem.A, "skipping because no private key specified")
					continue
				}
				if kem.Encap == nil {
					log.Fatalln("missing encap")
				}
				scheme := sntrup761.Scheme()
				if len(kem.Encap) != scheme.CiphertextSize()+X25519KeyLen {
					log.Fatalln("invalid encap len")
				}
				for _, prv := range prvs {
					if prv.A != sntrup761x25519.SNTRUP761X25519 {
						continue
					}
					if len(prv.V) != scheme.PrivateKeySize()+X25519KeyLen {
						log.Fatalln("invalid private keys len")
					}
					var ourSNTRUP sntrup761kem.PrivateKey
					ourSNTRUP, err = scheme.UnmarshalBinaryPrivateKey(
						prv.V[:scheme.PrivateKeySize()],
					)
					if err != nil {
						log.Fatal(err)
					}
					x25519 := ecdh.X25519()
					var ourX25519 *ecdh.PrivateKey
					ourX25519, err = x25519.NewPrivateKey(
						prv.V[scheme.PrivateKeySize():],
					)
					if err != nil {
						log.Fatal(err)
					}
					theirSNTRUP := kem.Encap[:scheme.CiphertextSize()]
					var keySNTRUP []byte
					keySNTRUP, err = scheme.Decapsulate(ourSNTRUP, theirSNTRUP)
					if err != nil {
						continue
					}
					var theirX25519 *ecdh.PublicKey
					theirX25519, err = x25519.NewPublicKey(
						kem.Encap[scheme.CiphertextSize():],
					)
					if err != nil {
						log.Fatal(err)
					}
					var keyX25519 []byte
					keyX25519, err = ourX25519.ECDH(theirX25519)
					if err != nil {
						log.Fatal(err)
					}
					{

						var ourSNTRUPPub []byte
						ourSNTRUPPub, err = ourSNTRUP.Public().MarshalBinary()
						if err != nil {
							log.Fatal(err)
						}
						ctHash := blake2b.Sum512(kem.Encap)
						pkHash := blake2b.Sum512(append(
							ourSNTRUPPub,
							ourX25519.PublicKey().Bytes()...,
						))
						ikm := bytes.Join([][]byte{
							keySNTRUP[:], keyX25519, ctHash[:], pkHash[:],
						}, []byte{})
						var prk []byte
						prk, err = hkdf.Extract(blake2bHash, ikm, nil)
						if err != nil {
							log.Fatal(err)
						}
						var kek []byte
						kek, err = hkdf.Expand(
							blake2bHash,
							prk,
							string(append(
								[]byte(cmenc.SNTRUP761X25519Info),
								encrypted.Id[:]...)),
							chacha20poly1305.KeySize,
						)
						if err != nil {
							log.Fatal(err)
						}
						var cekp []byte
						cekp, err = chapoly.Unwrap(kek, kem.CEK)
						if err != nil {
							log.Println(kemIdx, kem.A, err, ", skipping")
							continue
						}
						if len(cekp) != chapoly.CEKLen {
							log.Println(kemIdx, kem.A, "wrong key len, skipping")
							continue
						}
						cek = cekp
						break
					}
				}
			case mceliece6960119x25519.ClassicMcEliece6960119X25519HKDFSHAKE256:
				if len(prvs) == 0 {
					log.Println(kemIdx, kem.A, "skipping because no private key specified")
					continue
				}
				if kem.Encap == nil {
					log.Fatalln("missing encap")
				}
				if len(kem.Encap) != mceliece6960119.CiphertextSize+X25519KeyLen+chacha20poly1305.Overhead {
					log.Fatalln("invalid encap len")
				}
				for _, prv := range prvs {
					if prv.A != mceliece6960119x25519.ClassicMcEliece6960119X25519 {
						continue
					}
					if len(prv.V) != mceliece6960119.PrivateKeySize+X25519KeyLen {
						log.Fatalln("invalid private keys len")
					}
					var ourMcEliece *mceliece6960119.PrivateKey
					ourMcEliece, err = mceliece6960119.UnmarshalBinaryPrivateKey(
						prv.V[:len(prv.V)-X25519KeyLen],
					)
					if err != nil {
						log.Fatal(err)
					}
					x25519 := ecdh.X25519()
					var ourX25519 *ecdh.PrivateKey
					ourX25519, err = x25519.NewPrivateKey(prv.V[len(prv.V)-X25519KeyLen:])
					if err != nil {
						log.Fatal(err)
					}
					theirMcEliece := (kem.Encap)[:len(kem.Encap)-X25519KeyLen-chacha20poly1305.Overhead]
					theirX2559Encap := (kem.Encap)[len(kem.Encap)-X25519KeyLen-chacha20poly1305.Overhead:]
					var keyMcEliece []byte
					keyMcEliece, err = mceliece6960119.Decapsulate(
						ourMcEliece, theirMcEliece,
					)
					if err != nil {
						log.Fatal(err)
					}
					var theirX25519 *ecdh.PublicKey
					{
						var decapKeymat []byte
						decapKeymat, err = hkdf.Expand(
							cmhash.NewSHAKE256,
							keyMcEliece,
							string(append(
								[]byte(cmenc.ClassicMcEliece6960119X25519DecapInfo),
								encrypted.Id[:]...)),
							chacha20poly1305.KeySize+chacha20poly1305.NonceSizeX,
						)
						if err != nil {
							log.Fatal(err)
						}
						decapKey := decapKeymat[:chacha20poly1305.KeySize]
						decapNonce := decapKeymat[chacha20poly1305.KeySize:]
						var aead cipher.AEAD
						aead, err = chacha20poly1305.NewX(decapKey)
						if err != nil {
							log.Fatal(err)
						}
						var theirX25519Raw []byte
						theirX25519Raw, err = aead.Open(
							nil,
							decapNonce,
							theirX2559Encap,
							theirMcEliece)
						if err != nil {
							log.Fatal(err)
						}
						theirX25519, err = x25519.NewPublicKey(theirX25519Raw)
						if err != nil {
							log.Fatal(err)
						}
					}
					var keyX25519 []byte
					keyX25519, err = ourX25519.ECDH(theirX25519)
					if err != nil {
						log.Fatal(err)
					}
					{
						ourMcEliecePub := ourMcEliece.Public()
						var ourMcEliecePubRaw []byte
						ourMcEliecePubRaw, err = ourMcEliecePub.MarshalBinary()
						if err != nil {
							log.Fatal(err)
						}
						pkHash := cmhash.NewSHAKE256()
						pkHash.Write(ourMcEliecePubRaw)
						pkHash.Write(ourX25519.PublicKey().Bytes())
						ikm := bytes.Join([][]byte{
							keyMcEliece, keyX25519,
							sha3.SumSHAKE256(kem.Encap, 64),
							pkHash.Sum(nil),
						}, []byte{})
						var prk []byte
						prk, err = hkdf.Extract(cmhash.NewSHAKE256, ikm, nil)
						if err != nil {
							log.Fatal(err)
						}
						var kek []byte
						kek, err = hkdf.Expand(
							cmhash.NewSHAKE256,
							prk,
							string(append(
								[]byte(cmenc.ClassicMcEliece6960119X25519Info),
								encrypted.Id[:]...)),
							chacha20poly1305.KeySize,
						)
						if err != nil {
							log.Fatal(err)
						}
						var cekp []byte
						cekp, err = chapoly.Unwrap(kek, kem.CEK)
						if err != nil {
							log.Println(kemIdx, kem.A, err, ", skipping")
							continue
						}
						if len(cekp) != chapoly.CEKLen {
							log.Println(kemIdx, kem.A, "wrong key len, skipping")
							continue
						}
						cek = cekp
						break
					}
				}
			default:
				log.Println("unsupported KEM:", kem.A)
				continue
			}
			if cek != nil {
				break
			}
		}
		if cek == nil {
			log.Fatal("no KEMs processed")
		}
		if len(encrypted.Payload) > 0 {
			_, err = chapoly.Open(os.Stdout, bytes.NewReader(encrypted.Payload), cek, *parallel)
		} else {
			_, err = chapoly.OpenBlob(os.Stdout, os.Stdin, cek, *parallel)
		}
		if err != nil {
			log.Fatal(err)
		}
	} else {
		var id uuid.UUID
		if *setId == "" {
			id, err = uuid.NewRandom()
		} else {
			id, err = uuid.Parse(*setId)
		}
		if err != nil {
			log.Fatal(err)
		}
		var kems []cmenc.KEM
		cek = make([]byte, chapoly.CEKLen)
		rand.Read(cek)
		if *passphrase {
			var passwd []byte
			passwd, err = cmenc.ReadPasswd("Passphrase:")
			if err != nil {
				log.Fatal(err)
			}
			{
				var confirm []byte
				confirm, err = cmenc.ReadPasswd("Confirm:")
				if err != nil {
					log.Fatal(err)
				}
				if !bytes.Equal(passwd, confirm) {
					log.Fatal("passphrases do not match")
				}
			}
			salt := make([]byte, cmballoon.SaltLen)
			rand.Read(salt)
			kem := cmenc.KEM{
				A:    cmballoon.BalloonBLAKE2bHKDF,
				Salt: salt,
				BalloonCost: &ballooncost.Cost{
					S: uint64(*balloonS),
					T: uint64(*balloonT),
					P: uint64(*balloonP),
				},
			}
			{
				var kek []byte
				kek, err = hkdf.Expand(
					blake2bHash,
					balloon.H(blake2bHash, passwd, salt, *balloonS, *balloonT, *balloonP),
					string(append([]byte(cmballoon.HKDFInfo), id[:]...)),
					chacha20poly1305.KeySize,
				)
				if err != nil {
					log.Fatal(err)
				}
				var cekp []byte
				cekp, err = chapoly.Wrap(kek, cek)
				if err != nil {
					log.Fatal(err)
				}
				kem.CEK = cekp
			}
			kems = append(kems, kem)
		}
		for pubId, pub := range pubs {
			switch pub.A {
			case sntrup761x25519.SNTRUP761X25519:
				scheme := sntrup761.Scheme()
				if len(pub.V) != scheme.PublicKeySize()+X25519KeyLen {
					log.Fatalln("invalid public keys len")
				}
				var theirSNTRUP sntrup761kem.PublicKey
				theirSNTRUP, err = scheme.UnmarshalBinaryPublicKey(
					pub.V[:scheme.PublicKeySize()],
				)
				if err != nil {
					log.Fatal(err)
				}
				x25519 := ecdh.X25519()
				var theirX25519 *ecdh.PublicKey
				theirX25519, err = x25519.NewPublicKey(
					pub.V[scheme.PublicKeySize():],
				)
				if err != nil {
					log.Fatal(err)
				}
				var ciphertext []byte
				var keySNTRUP []byte
				ciphertext, keySNTRUP, err = scheme.Encapsulate(theirSNTRUP)
				if err != nil {
					log.Fatal(err)
				}
				var ourPrvX25519 *ecdh.PrivateKey
				ourPrvX25519, err = ecdh.X25519().GenerateKey(rand.Reader)
				if err != nil {
					log.Fatal(err)
				}
				ourPubX25519 := ourPrvX25519.PublicKey()
				var keyX25519 []byte
				keyX25519, err = ourPrvX25519.ECDH(theirX25519)
				if err != nil {
					log.Fatal(err)
				}
				kem := cmenc.KEM{
					A:     sntrup761x25519.SNTRUP761X25519HKDFBLAKE2b,
					Encap: append(ciphertext, ourPubX25519.Bytes()...),
				}
				{
					ctHash := blake2b.Sum512(kem.Encap)
					pkHash := blake2b.Sum512(pub.V)
					ikm := bytes.Join([][]byte{
						keySNTRUP, keyX25519, ctHash[:], pkHash[:],
					}, []byte{})
					var prk []byte
					prk, err = hkdf.Extract(blake2bHash, ikm, nil)
					if err != nil {
						log.Fatal(err)
					}
					var kek []byte
					kek, err = hkdf.Expand(
						blake2bHash,
						prk,
						string(append([]byte(cmenc.SNTRUP761X25519Info), id[:]...)),
						chacha20poly1305.KeySize,
					)
					if err != nil {
						log.Fatal(err)
					}
					var cekp []byte
					cekp, err = chapoly.Wrap(kek, cek)
					if err != nil {
						log.Fatal(err)
					}
					kem.CEK = cekp
				}
				if *includeTo {
					kem.To = pubIds[pubId]
				}
				kems = append(kems, kem)
			case mceliece6960119x25519.ClassicMcEliece6960119X25519:
				if len(pub.V) != mceliece6960119.PublicKeySize+X25519KeyLen {
					log.Fatalln("invalid public keys len")
				}
				var theirMcEliece *mceliece6960119.PublicKey
				theirMcEliece, err = mceliece6960119.UnmarshalBinaryPublicKey(
					pub.V[:len(pub.V)-X25519KeyLen],
				)
				if err != nil {
					log.Fatal(err)
				}
				x25519 := ecdh.X25519()
				var theirX25519 *ecdh.PublicKey
				theirX25519, err = x25519.NewPublicKey(pub.V[len(pub.V)-X25519KeyLen:])
				if err != nil {
					log.Fatal(err)
				}
				var ciphertext []byte
				var keyMcEliece []byte
				ciphertext, keyMcEliece, err = mceliece6960119.Encapsulate(theirMcEliece)
				if err != nil {
					log.Fatal(err)
				}
				var ourPrvX25519 *ecdh.PrivateKey
				ourPrvX25519, err = ecdh.X25519().GenerateKey(rand.Reader)
				if err != nil {
					log.Fatal(err)
				}
				ourPubX25519 := ourPrvX25519.PublicKey()
				kem := cmenc.KEM{
					A: mceliece6960119x25519.ClassicMcEliece6960119X25519HKDFSHAKE256,
				}
				{
					var decapKeymat []byte
					decapKeymat, err = hkdf.Expand(
						cmhash.NewSHAKE256,
						keyMcEliece,
						string(append(
							[]byte(cmenc.ClassicMcEliece6960119X25519DecapInfo),
							id[:]...)),
						chacha20poly1305.KeySize+chacha20poly1305.NonceSizeX,
					)
					if err != nil {
						log.Fatal(err)
					}
					decapKey := decapKeymat[:chacha20poly1305.KeySize]
					decapNonce := decapKeymat[chacha20poly1305.KeySize:]
					var aead cipher.AEAD
					aead, err = chacha20poly1305.NewX(decapKey)
					if err != nil {
						log.Fatal(err)
					}
					kem.Encap = aead.Seal(
						ciphertext[:],
						decapNonce,
						ourPubX25519.Bytes(),
						ciphertext)
				}
				var keyX25519 []byte
				keyX25519, err = ourPrvX25519.ECDH(theirX25519)
				if err != nil {
					log.Fatal(err)
				}
				{
					ikm := bytes.Join([][]byte{
						keyMcEliece[:], keyX25519,
						sha3.SumSHAKE256(kem.Encap, 64),
						sha3.SumSHAKE256(pub.V, 64),
					}, []byte{})
					var prk []byte
					prk, err = hkdf.Extract(cmhash.NewSHAKE256, ikm, nil)
					if err != nil {
						log.Fatal(err)
					}
					var kek []byte
					kek, err = hkdf.Expand(
						cmhash.NewSHAKE256,
						prk,
						string(append(
							[]byte(cmenc.ClassicMcEliece6960119X25519Info),
							id[:]...)),
						chacha20poly1305.KeySize,
					)
					if err != nil {
						log.Fatal(err)
					}
					var cekp []byte
					cekp, err = chapoly.Wrap(kek, cek)
					if err != nil {
						log.Fatal(err)
					}
					kem.CEK = cekp
				}
				if *includeTo {
					kem.To = pubIds[pubId]
				}
				kems = append(kems, kem)
			default:
				log.Fatalln("unsupported KEM:", pub.A)
			}
		}
		if len(kems) == 0 {
			log.Fatal("no KEMs specified")
		}
		{
			var hdr bytes.Buffer
			if _, err = keks.Encode(&hdr, cmenc.Magic, nil); err != nil {
				log.Fatal(err)
			}
			enc := cmenc.Encrypted{
				Id:  id,
				KEM: kems,
				DEM: cmenc.DEM{A: chapoly.DEMAlgo},
			}
			if *noblob {
				var buf bytes.Buffer
				if _, err = chapoly.Seal(&buf, os.Stdin, cek, *parallel); err != nil {
					log.Fatal(err)
				}
				enc.Payload = buf.Bytes()
			}
			if _, err = keks.Encode(&hdr, &enc, nil); err != nil {
				log.Fatal(err)
			}
			if _, err = io.Copy(os.Stdout, &hdr); err != nil {
				log.Fatal(err)
			}
		}
		if !*noblob {
			if _, err = chapoly.SealBlob(os.Stdout, os.Stdin, cek, *parallel); err != nil {
				log.Fatal(err)
			}
		}
	}
}
