Skip to content

fix(base): report a zero expiration for keys that never expire - #250

Merged
ffumero2003 merged 6 commits into
mainfrom
fix-base-issue-244-key-expires-zero
Oct 30, 2026
Merged

ffumero2003 merged 6 commits into
mainfrom
fix-base-issue-244-key-expires-zero

Conversation

@ffumero2003

@ffumero2003 ffumero2003 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Problem

b2_create_key and b2_list_keys return expirationTimestamp: null for an application key with no expiration. internal/b2types decodes it into an int64, so a null or absent value becomes 0, and base mapped it with millitime(0) = time.Unix(0, 0). Key.Expires() therefore returned 1970-01-01 with IsZero() == false, so callers couldn't tell "never expires" from "expired in 1970".

Fix

  • base/base.go: a small key-specific helper, keyExpiration, maps 0 to the zero time.Time and otherwise calls millitime. CreateKey and ListKeys both use it. millitime itself is unchanged, because it also maps file upload timestamps, where 0 means something else.
  • Godoc: Key.Expires() (b2) and base.Key.Expires say the zero time means the key never expires, and that callers should check IsZero(). Not Unix(), which is -62135596800 for the zero time.
  • Changelog (Fixed), with the migration hint: callers comparing Expires() to 1970-01-01, or Unix() to 0, should use IsZero().

Null and absent expirationTimestamp both decode to 0 in b2types.Key, and both mean "never expires", so both map to the zero time. b2types.AuthorizeAccountResponse.KeyExpiration has the same null-to-0 pattern, but nothing reads it yet, so it's left alone.

Verification

New tests in base/key_expiration_test.go and b2/key_expiration_wire_test.go cover create and list with null, with the field absent, and with a literal timestamp (1700000000123 → 2023-11-14T22:13:20.123Z).

Break Failing output
Restore millitime for keys CreateKey Expires = 1969-12-31 …, want zero time (8 assertions: null and absent, create and list, base and b2)
Map every expiration to zero CreateKey Expires = 0001-01-01 …, want 2023-11-14 22:13:20.123 +0000 UTC

With no B2 credentials set: go test -race -count=3 -shuffle=on ./..., go vet ./... and make ci pass.

Live check: the existing live tests that create a key without a lifetime now assert Expires().IsZero() on the created key and on the same key returned by ListKeys (b2/integration_test.go). They compile and run in the live CI job on main; they were not run locally (no credentials).

Other millitime callers (#244 asked to note any with the same null case): for upload, finish-large-file, copy, hide, get-file-info and unfinished-large-file timestamps, the B2 docs define no null or zero "no value" meaning. The one documented exception is virtual folder entries in delimiter listings (action: "folder", uploadTimestamp: 0), which still map to 1970. That is filed separately as #253.

make ci (tail)

go build ./...
go vet ./...
go mod tidy -diff
go mod verify
all modules verified
0 issues.
go test -race -shuffle=on ./...
ok  	github.com/Backblaze/blazer/b2	15.260s
ok  	github.com/Backblaze/blazer/b2/sync	4.724s
ok  	github.com/Backblaze/blazer/base	8.328s
ok  	github.com/Backblaze/blazer/internal/b2types	1.817s
ok  	github.com/Backblaze/blazer/internal/b2wiretest	2.345s
ok  	github.com/Backblaze/blazer/internal/blog	2.996s
ok  	github.com/Backblaze/blazer/internal/retry	2.415s
ok  	github.com/Backblaze/blazer/x/b2sim	3.563s
ok  	github.com/Backblaze/blazer/x/consistent	2.678s
ok  	github.com/Backblaze/blazer/x/transport	3.058s
ok  	github.com/Backblaze/blazer/x/window	3.393s
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./...
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build ./...
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build ./...
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build ./...
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build ./...
... scripts/*_test.sh: all ok

Closes #244

@david-santacruz-backblaze david-santacruz-backblaze left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good mate, a few small things to check before:

  • Live check: could we assert key.Expires().IsZero() in an existing live test that creates a key, such as TestListBucketsWithKey? If not, a line in the PR saying that check was not run would do.
  • Comment on keyExpiration: a short note on why 0 maps to the zero time and millitime is left alone would help the next reader.
  • Other millitime callers: #244 asks to note any with the same null case. Could you confirm the upload timestamps can't be null?
  • Nit: 5bb7db1 is titled test(...) but also touches the godoc and changelog.

@ffumero2003

Copy link
Copy Markdown
Contributor Author

@david-santacruz-backblaze Thanks, all four:

  • Live check: added (c0484e4). The existing live tests that create a key without a lifetime now assert Expires().IsZero() on the created key and on the same key returned by ListKeys. They compile; they run in the live job on main, not locally (no credentials).
  • Comment on keyExpiration: added (aad79e8), explaining why 0 maps to the zero time and why millitime stays as is.
  • Other millitime callers: checked. For upload, finish-large-file, copy, hide, get-file-info and unfinished-large-file timestamps, the B2 docs define no null or zero "no value" meaning. The one documented exception is virtual folder entries in delimiter listings (action: "folder", uploadTimestamp: 0), which still map to 1970. It's low impact, because Object.Attrs() re-resolves a folder by name rather than using the listing record, so it's filed separately as fix(base): virtual folder entries report a 1970 upload timestamp #253.
  • Commit title nit: agreed. The squash merge uses the PR title, so 5bb7db1's title won't land on main.

The branch is synced with main, and the description now has the live-check note, the millitime audit and the make ci tail.

@ffumero2003
ffumero2003 merged commit e922ed8 into main Oct 30, 2026
23 of 24 checks passed
@ffumero2003
ffumero2003 deleted the fix-base-issue-244-key-expires-zero branch October 30, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(base): Key.Expires() returns 1970-01-01 for keys with no expiration

2 participants